import { getPayload } from 'payload'; import configPromise from '../payload.config'; import * as dotenv from 'dotenv'; dotenv.config(); import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import { parseMarkdownToLexical } from '../src/payload/utils/lexicalParser'; async function mapImageToMediaId(payload: any, imagePath: string): Promise { if (!imagePath) return null; const filename = path.basename(imagePath); // Exact match instead of substring to avoid matching "cable-black.jpg" with "cable.jpg" const media = await payload.find({ collection: 'media', where: { filename: { equals: filename, }, }, limit: 1, }); if (media.docs.length > 0) { return media.docs[0].id; } const cleanPath = imagePath.startsWith('/') ? imagePath.slice(1) : imagePath; const fullPath = path.join(process.cwd(), 'public', cleanPath); if (fs.existsSync(fullPath)) { try { console.log(`[Products Migration] šŸ“¤ Ingesting missing Media into Payload: ${filename}`); const newMedia = await payload.create({ collection: 'media', data: { alt: filename.replace(/[-_]/g, ' ').replace(/\.[^/.]+$/, ''), }, filePath: fullPath, }); return newMedia.id; } catch (err: any) { console.error(`[Products Migration] āŒ Failed to ingest ${filename}:`, err); } } else { console.warn(`[Products Migration] āš ļø Missing image entirely from disk: ${fullPath}`); } return null; } export async function migrateProducts() { const payload = await getPayload({ config: configPromise }); const productLocales = ['en', 'de']; for (const locale of productLocales) { const productsDir = path.join(process.cwd(), 'data', 'products', locale); if (!fs.existsSync(productsDir)) continue; // Recursive file finder const mdFiles: string[] = []; const walk = (dir: string) => { const files = fs.readdirSync(dir); for (const file of files) { const fullPath = path.join(dir, file); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { walk(fullPath); } else if (file.endsWith('.mdx')) { mdFiles.push(fullPath); } } }; walk(productsDir); for (const filePath of mdFiles) { const fileContent = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContent); console.log(`Processing Product: [${locale.toUpperCase()}] ${data.title}`); // 1. Process Images const mediaIds = []; if (data.images && Array.isArray(data.images)) { for (const imgPath of data.images) { const id = await mapImageToMediaId(payload, imgPath); if (id) mediaIds.push(id); } } // 2. Map Lexical AST for deeply nested components (like ProductTabs + Technical data) const lexicalContent = parseMarkdownToLexical(content); const wrapLexical = (blocks: any[]) => ({ root: { type: 'root', format: '', indent: 0, version: 1, children: blocks, direction: 'ltr', }, }); // Payload expects category objects via the 'category' key const formattedCategories = Array.isArray(data.categories) ? data.categories.map((c: string) => ({ category: c })) : []; const productData = { title: data.title, sku: data.sku || path.basename(filePath, '.mdx'), slug: path.basename(filePath, '.mdx'), locale: locale as 'en' | 'de', categories: formattedCategories, description: data.description || '', featuredImage: mediaIds.length > 0 ? mediaIds[0] : undefined, images: mediaIds.length > 0 ? mediaIds : undefined, content: wrapLexical(lexicalContent) as any, application: data.application ? (wrapLexical(parseMarkdownToLexical(data.application)) as any) : undefined, _status: 'published' as any, }; // Check if product exists (by sku combined with locale, since slug may differ by language) const existing = await payload.find({ collection: 'products', where: { and: [{ slug: { equals: productData.slug } }, { locale: { equals: locale } }], }, }); if (existing.docs.length > 0) { console.log(`Updating existing product ${productData.slug} (${locale})`); await payload.update({ collection: 'products', id: existing.docs[0].id, data: productData, }); } else { console.log(`Creating new product ${productData.slug} (${locale})`); await payload.create({ collection: 'products', data: productData, }); } } } console.log(`\nāœ… Products Migration Complete!`); process.exit(0); } migrateProducts().catch(console.error);