Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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: 5 additions & 11 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ fileignoreconfig:

- filename: api/src/services/migration.service.ts
checksum: bd5374b31c0fad4a266bc9affdcf595f71456edb68b075c1fdfba762f7462e38

- filename: ui/src/components/AuditLogs/index.tsx
checksum: e1402f3cd5b96328d5e1d1e5f2a7fa5179858d6133db831b1b96714eb0f8c260

- filename: .github/workflows/secrets-scan.yml
ignore_detectors:
Expand Down Expand Up @@ -53,7 +50,7 @@ fileignoreconfig:
checksum: f37809ddd67b8ad143d7d9fbb2c305f7c9150a8eec1f3325724fca576c736656

- filename: ui/src/components/AuditLogs/index.tsx
checksum: 51ce05f66ac49023452f200e587389e55c88357102a61e77eb468d0e02b52846
checksum: 8b78783b54935cc1e9721ab2b0b4daf768f5a1dbd96e6594e2eeb1d5fd45d90f

- filename: ui/src/components/ContentMapper/index.tsx
checksum: b5f66e808ecf4461ccb5c4fde937da1e6d9e640a2521d6d85858a22261df6571
Expand Down Expand Up @@ -83,11 +80,8 @@ fileignoreconfig:
checksum: 4abcc89c75b7ddca8128fd72faafbb9b159f02611d96325bcd355283074ce287
- filename: ui/src/components/ContentMapper/index.scss
checksum: 46f8fde3b745feba40943f00d8d52714d0f320202c86b62f6c5ded73c2dbcf4c


fileignoreconfig:
- filename: api/src/services/migration.service.ts
checksum: abae249cacfab5c67e2d18a645c852649da2339954f26ae0f88c8698bdc016e6
- filename: api/src/services/wordpress.service.ts
checksum: 95e532836194547682e791d0a231fb70e402be99260ac234e808eddb6e80c6af
- filename: api/src/services/migration.service.ts
checksum: abae249cacfab5c67e2d18a645c852649da2339954f26ae0f88c8698bdc016e6
- filename: api/src/services/wordpress.service.ts
checksum: 95e532836194547682e791d0a231fb70e402be99260ac234e808eddb6e80c6af
version: "1.0"
188 changes: 85 additions & 103 deletions api/src/services/wordpress.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2260,19 +2260,22 @@ function limitConcurrency(maxConcurrency: number) {
}
const limit = limitConcurrency(5);

async function featuredImageMapping(postid: string, post: any, postdata: any) {
async function featuredImageMapping(postid: string, post: any, postdata: any, assetsSchemaPath: string) {
try {
const filePath = path.join(process.cwd(), assetsSave, MIGRATION_DATA_CONFIG.ASSETS_SCHEMA_FILE);
const fileContent = fs.readFileSync(filePath, 'utf8').trim();
// **THE FIX: Use the path argument directly, don't use the global variable**
const fileContent = fs.readFileSync(assetsSchemaPath, 'utf8').trim();

if (!fileContent) {
throw new Error(`File ${filePath} is empty or missing`);
}
const assetsId = JSON?.parse(fileContent);
if (!post["wp:postmeta"] || !assetsId) return;
if (!fileContent) {
// File is empty, so no assets have been processed. Nothing to map.
return postdata;
}

const postmetaArray = Array.isArray(post["wp:postmeta"]) ? post["wp:postmeta"]
: [post["wp:postmeta"]];
const assetsId = JSON.parse(fileContent);
if (!post["wp:postmeta"] || !assetsId) {
return postdata;
}

const postmetaArray = Array.isArray(post["wp:postmeta"]) ? post["wp:postmeta"] : [post["wp:postmeta"]];

const assetsDetails = postmetaArray
.filter((meta) => meta["wp:meta_key"] === "_thumbnail_id")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta?.["wp:meta_key"]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Array.isArray(post?.["wp:postmeta"])
? post["wp:postmeta"]
: (post?.["wp:postmeta"] ? [post["wp:postmeta"]] : [])

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

meta?.["wp:meta_key"]

Expand All @@ -2283,14 +2286,17 @@ async function featuredImageMapping(postid: string, post: any, postdata: any) {
(asset: any) => asset.uid === attachmentid
);
})
.filter(Boolean); // Filter out undefined matches
.filter(Boolean);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const assetsDetails = postmetaArray
?.filter?.((meta) => meta?.["wp:meta_key"] === "_thumbnail_id")
.map((meta) => {
const attachmentid = assets_${meta?.["wp:meta_value"]};

    return Object.values(assetsId)?.find(
      (asset: any) => asset?.uid === attachmentid
    );
  })
  ?.filter(Boolean); 


if (assetsDetails?.length > 0) {
if (assetsDetails.length > 0 && postdata[postid]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assetsDetails?.length > 0 && postdata?.[postid]

// Assign the found asset object to the 'featured_image' field
postdata[postid]["featured_image"] = assetsDetails[0];
}
return postdata;
} catch (error) {
console.error(error);
console.error("Error during featured image mapping:", error);
// Return the original postdata if an error occurs to avoid losing the entry
return postdata;
}
}

Expand Down Expand Up @@ -2383,96 +2389,67 @@ async function processChunkData(
isLastChunk: boolean,
contenttype: any,
authorsFilePath: string,
referencesFilePath: string
referencesFilePath: string,
assetsSchemaPath: string
) {
const postdata: any = {};
const formattedPosts: any = {};
let postdataCombined = {};
let postdataCombined: Record<string, any> = {};

try {
const writePromises = [];
// This filter is good, it correctly selects only post-like items.
const filteredChunk = chunkData?.filter((item: any) =>
item["wp:post_type"] !== "page" &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?.

item["wp:post_type"] !== "attachment" &&
["publish", "inherit", "draft"]?.includes(item["wp:status"])
);

const filteredChunk = chunkData?.filter((item:any) => item["wp:post_type"] === "post" && ["publish", "inherit", "draft"]?.includes(item["wp:status"]));
// The main loop processes one item at a time.
for (const data of filteredChunk) {
writePromises.push(
limit(async () => {
const { postCategories, postTags, postTerms } = extractPostCategories(data["category"], referencesFilePath);


// Extract author
const postAuthor = extractPostAuthor(data["dc:creator"], authorsFilePath);

const dom = new JSDOM(
data["content:encoded"]
.replace(/<!--.*?-->/g, "")
.replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, ""),
{ virtualConsole }
);
const htmlDoc = dom.window.document.querySelector("body");
const jsonValue = htmlToJson(htmlDoc);

// Format date safely
let postDate: string | null = null;
try {
const parsed = new Date(data["wp:post_date_gmt"]);
if (!isNaN(parsed.getTime())) {
postDate = parsed.toISOString();
}
} catch (error) {
console.error(`Error parsing date for post ${data["wp:post_id"]}:`, error);
}

const base = blog_base_url?.split("/")?.filter(Boolean);
const blogname = base[base?.length - 1];
const url = data["link"]?.split(blogname)[1];
const uid = `posts_${data["wp:post_id"]}`;
const customId = idCorrector(uid);

postdata[customId] = {
title: data["title"] || `Posts - ${data["wp:post_id"]}`,
uid: customId,
url: url,
date: postDate,
full_description: jsonValue,
excerpt: (data["excerpt:encoded"] || "")
.replace(/<!--.*?-->/g, "")
.replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, ""),
author: postAuthor,
category: postCategories,
terms: postTerms,
tag: postTags,
featured_image: '',
publish_details: [],
};

for (const [key, value] of Object.entries(postdata as { [key: string]: any })) {
const customId = idCorrector(value?.uid);
formattedPosts[customId] = {
...formattedPosts[customId],
uid: customId,
...(await mapContentTypeToEntry(contenttype, value)),
};
formattedPosts[customId].publish_details = [];
}
const customId = idCorrector(`posts_${data["wp:post_id"]}`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data?.["wp:post_id"]


// Step 1: Resolve all references for the CURRENT post
const { postCategories, postTags, postTerms } = extractPostCategories(data["category"], referencesFilePath);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

data?.["category"]

or for global use if(data)

const postAuthor = extractPostAuthor(data["dc:creator"], authorsFilePath);
const jsonValue = htmlToJson(new JSDOM(data["content:encoded"].replace("//g", "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, "")).window.document.querySelector("body"));
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern "//g" in the replace function is incorrect. It should be /<!--.*?-->/g to properly remove HTML comments.

Suggested change
const jsonValue = htmlToJson(new JSDOM(data["content:encoded"].replace("//g", "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, "")).window.document.querySelector("body"));
const jsonValue = htmlToJson(new JSDOM(data["content:encoded"].replace(/<!--.*?-->/gs, "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, "")).window.document.querySelector("body"));

Copilot uses AI. Check for mistakes.
let postDate = null;
if (data["wp:post_date_gmt"] && !data["wp:post_date_gmt"].startsWith("0000")) {
postDate = new Date(data["wp:post_date_gmt"]).toISOString();
}

const formattedPostsWithImage = await featuredImageMapping(
`posts_${data["wp:post_id"]}`,
data,
formattedPosts
);
// Step 2: Create a temporary object with all raw data for the CURRENT post
const rawPostData = {
title: data["title"] || `Posts - ${data["wp:post_id"]}`,
uid: customId,
url: data["link"]?.split(blog_base_url?.split("/").filter(Boolean).pop())[1],
date: postDate,
full_description: jsonValue,
excerpt: (data["excerpt:encoded"] || "").replace("//g", "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, ""),
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern "//g" in the replace function is incorrect. It should be /<!--.*?-->/g to properly remove HTML comments.

Suggested change
excerpt: (data["excerpt:encoded"] || "").replace("//g", "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, ""),
excerpt: (data["excerpt:encoded"] || "").replace(/<!--.*?-->/gs, "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, ""),

Copilot uses AI. Check for mistakes.
author: postAuthor,
category: postCategories,
terms: postTerms,
tag: postTags,
featured_image: '',
publish_details: [],
};

// Step 3: Create the final, formatted post object using mapContentTypeToEntry
let formattedPost = {
uid: customId,
...(await mapContentTypeToEntry(contenttype, rawPostData)),
publish_details: [],
};

postdataCombined = { ...postdataCombined, ...formattedPostsWithImage };
})
// Step 4: Map the featured image for ONLY the CURRENT post
const formattedPostWithImage = await featuredImageMapping(
customId, // Use the corrected ID
data,
{ [customId]: formattedPost }, // Pass an object with only the current post
assetsSchemaPath
);
}

const results: any = await Promise.all(writePromises);
const allSuccess = results.every(
(result: any) => typeof result !== "object" || result?.success
);

if (isLastChunk && allSuccess) {
console.info("last data");

// Step 5: Add the final, complete post to the combined results
if (formattedPostWithImage && formattedPostWithImage[customId]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formattedPostWithImage?.[customId]

postdataCombined[customId] = formattedPostWithImage[customId];
}
}

return postdataCombined;
Expand All @@ -2498,6 +2475,9 @@ async function extractPosts( packagePath: string, destinationStackId: string, pr
referencesFolder = path.join(MIGRATION_DATA_CONFIG.DATA, destinationStackId, MIGRATION_DATA_CONFIG.REFERENCES_DIR_NAME);
const referencesFilePath = path.join(referencesFolder, MIGRATION_DATA_CONFIG.REFERENCES_FILE_NAME);

assetsSave = path.join(MIGRATION_DATA_CONFIG.DATA, destinationStackId, MIGRATION_DATA_CONFIG.ASSETS_DIR_NAME);
const assetsSchemaPath = path.join(assetsSave, MIGRATION_DATA_CONFIG.ASSETS_SCHEMA_FILE);

const alldata: any = await fs.promises.readFile(packagePath, "utf8");
const alldataParsed = JSON.parse(alldata);
blog_base_url =
Expand All @@ -2515,7 +2495,7 @@ async function extractPosts( packagePath: string, destinationStackId: string, pr
const chunkData = JSON.parse(data);
const isLastChunk = filename === lastChunk;

const chunkPostData = await processChunkData(chunkData, filename, isLastChunk, contenttype, authorsFilePath, referencesFilePath);
const chunkPostData = await processChunkData(chunkData, filename, isLastChunk, contenttype, authorsFilePath, referencesFilePath, assetsSchemaPath);
postdataCombined = { ...postdataCombined, ...chunkPostData };

const seenTitles = new Map();
Expand Down Expand Up @@ -2656,7 +2636,8 @@ const extractPageParent = (parentId?: string): any[] => {
async function handlePagesChunkData(
items: any[],
contenttype: any,
authorsFilePath: string
authorsFilePath: string,
assetsSchemaPath: string
): Promise<Record<string, any>> {
const pageDataCombined: Record<string, any> = {};

Expand All @@ -2666,18 +2647,16 @@ async function handlePagesChunkData(

for (const item of items) {
if (!allowedPageTypes.includes(item['wp:post_type']) || !allowedStatuses.includes(item['wp:status'])) {
continue; // Skip items that aren't valid pages
continue;
}

const uid = `pages_${item['wp:post_id']}`;
const customId = idCorrector(uid);

// 1. Resolve references for the current item
const authorRef = extractPageAuthor(item['dc:creator'], authorsFilePath);
const parentRef = extractPageParent(item['wp:post_parent']);
const body = htmlToJson(new JSDOM(item["content:encoded"].replace("//g", "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, "")).window.document.querySelector('body'));
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex pattern "//g" in the replace function is incorrect. It should be /<!--.*?-->/g to properly remove HTML comments.

Suggested change
const body = htmlToJson(new JSDOM(item["content:encoded"].replace("//g", "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, "")).window.document.querySelector('body'));
const body = htmlToJson(new JSDOM(item["content:encoded"].replace(/<!--.*?-->/g, "").replace(/&lt;!--?\s+\/?wp:.*?--&gt;/g, "")).window.document.querySelector('body'));

Copilot uses AI. Check for mistakes.

// 2. Create a temporary object with all the raw data
const rawPageData = {
uid: customId,
title: item['title'] || 'Untitled',
Expand All @@ -2701,9 +2680,10 @@ async function handlePagesChunkData(


const formattedPageWithImage = await featuredImageMapping(
`pages_${item["wp:post_id"]}`,
customId,
item,
{ [customId]: formattedPage }
{ [customId]: formattedPage },
assetsSchemaPath
);


Expand Down Expand Up @@ -2743,6 +2723,8 @@ async function extractPages(
await startingDirPages(ct, master_locale, project?.locales);
const authorsCtName = keyMapper?.["authors"] || MIGRATION_DATA_CONFIG.AUTHORS_DIR_NAME;
const authorsFilePath = path.join(entrySave, authorsCtName, master_locale, `${master_locale}.json`);
assetsSave = path.join(MIGRATION_DATA_CONFIG.DATA, destinationStackId, MIGRATION_DATA_CONFIG.ASSETS_DIR_NAME);
const assetsSchemaPath = path.join(assetsSave, MIGRATION_DATA_CONFIG.ASSETS_SCHEMA_FILE);
const alldata: any = await fs.promises.readFile(packagePath, "utf8");
const alldataParsed = JSON.parse(alldata);
blog_base_url =
Expand All @@ -2765,7 +2747,7 @@ async function extractPages(

const isLastChunk = filename === lastChunk;

const chunkPages = await handlePagesChunkData(chunkData, contenttype, authorsFilePath);
const chunkPages = await handlePagesChunkData(chunkData, contenttype, authorsFilePath, assetsSchemaPath);

console.info(
`${filename} → Mapped entries: ${Object.keys(chunkPages).length}`
Expand Down
8 changes: 4 additions & 4 deletions ui/src/components/LogScreen/MigrationLogViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => {
notificationContent: { text: message },
notificationProps: {
position: 'bottom-center',
hideProgressBar: false
hideProgressBar: true
},
type: 'success'
});
Expand Down Expand Up @@ -242,7 +242,7 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => {
{newMigrationData?.migration_execution?.migrationCompleted ? (
<div>
<div className="log-entry text-center">
<div className="log-message">
<div className="log-message generic-log-message">
Migration Execution process is completed in the selected stack
<Link href={stackLink} target="_blank" className="ml-5">
<strong>{newMigrationData?.stackDetails?.label}</strong>
Expand Down Expand Up @@ -277,7 +277,7 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => {
style={logStyles[level || ''] || logStyles.info}
className="log-entry text-center"
>
<div className="log-message">
<div className="log-message generic-log-message">
Migration has already done in selected stack. Please create a new project.
</div>
</div>
Expand All @@ -292,7 +292,7 @@ const MigrationLogViewer = ({ serverPath }: LogsType) => {
style={logStyles[level || ''] || logStyles.info}
className="log-entry text-center"
>
<div className="log-message">{message}</div>
<div className="log-message generic-log-message">{message}</div>
</div>
) : (
<div
Expand Down
3 changes: 3 additions & 0 deletions ui/src/components/LogScreen/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,8 @@
.log-message {
flex-grow: 1;
padding: 0 $px-16;
&.generic-log-message {
padding: $px-16;
}
}
}
6 changes: 3 additions & 3 deletions ui/src/components/LogScreen/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ const TestMigrationLogViewer = ({ serverPath, sendDataToParent, projectId }: Log
notificationContent: { text: message },
notificationProps: {
position: 'bottom-center',
hideProgressBar: false
hideProgressBar: true
},
type: 'success'
});
Expand Down Expand Up @@ -260,7 +260,7 @@ const TestMigrationLogViewer = ({ serverPath, sendDataToParent, projectId }: Log
{migratedStack?.isMigrated ? (
<div>
<div className="log-entry text-center">
<div className="log-message">
<div className="log-message generic-log-message">
Test Migration is completed for stack{' '}
<Link href={newMigrationData?.test_migration?.stack_link} target="_blank">
<strong>{migratedStack?.stackName}</strong>
Expand Down Expand Up @@ -293,7 +293,7 @@ const TestMigrationLogViewer = ({ serverPath, sendDataToParent, projectId }: Log
style={logStyles[level || ''] || logStyles.info}
className="log-entry text-center"
>
<div className="log-message">{message}</div>
<div className="log-message generic-log-message">{message}</div>
</div>
) : (
<div
Expand Down
Loading