import { getPayload } from 'payload'; import configPromise from '@payload-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); const media = await payload.find({ collection: 'media', where: { filename: { equals: filename, }, }, limit: 1, }); if (media.docs.length > 0) { return media.docs[0].id; } // Auto-ingest missing images from legacy public/ directory const cleanPath = imagePath.startsWith('/') ? imagePath.slice(1) : imagePath; const fullPath = path.join(process.cwd(), 'public', cleanPath); if (fs.existsSync(fullPath)) { try { console.log(`[Blog Migration] 📤 Ingesting missing Media into Payload: ${filename}`); const newMedia = await payload.create({ collection: 'media', data: { alt: filename.replace(/[-_]/g, ' ').replace(/\.[^/.]+$/, ''), // create a human readable alt text }, filePath: fullPath, }); return newMedia.id; } catch (err: any) { console.error(`[Blog Migration] ❌ Failed to ingest ${filename}:`, err); } } else { console.warn(`[Blog Migration] ⚠️ Missing image entirely from disk: ${fullPath}`); } return null; } async function migrateBlogPages() { const payload = await getPayload({ config: configPromise }); const locales = ['en', 'de']; for (const locale of locales) { const pagesDir = path.join(process.cwd(), 'data', 'pages', locale); if (!fs.existsSync(pagesDir)) continue; const files = fs.readdirSync(pagesDir); for (const file of files) { if (!file.endsWith('.mdx')) continue; const slug = file.replace(/\.mdx$/, ''); const filePath = path.join(pagesDir, file); const fileContent = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContent); console.log(`Migrating ${locale}/${slug}...`); const lexicalBlocks = parseMarkdownToLexical(content); const lexicalAST = { root: { type: 'root', format: '', indent: 0, version: 1, children: lexicalBlocks, direction: 'ltr', }, }; const status = data.public === false ? 'draft' : 'published'; let featuredImageId = null; if (data.featuredImage || data.image) { featuredImageId = await mapImageToMediaId(payload, data.featuredImage || data.image); } try { // Find existing post const existing = await payload.find({ collection: 'pages', where: { slug: { equals: slug }, locale: { equals: locale } as any }, }); if (slug.includes('welcome-to-the-future')) { console.log(`\n--- AST for ${slug} ---`); console.log(JSON.stringify(lexicalAST, null, 2)); console.log(`-----------------------\n`); } if (existing.docs.length > 0) { await payload.update({ collection: 'pages', id: existing.docs[0].id, data: { content: lexicalAST as any, ...(featuredImageId ? { featuredImage: featuredImageId } : {}), }, }); console.log(`✅ AST Components & Image RE-INJECTED for ${slug}`); } else { await payload.create({ collection: 'pages', data: { title: data.title, slug: slug, locale: locale, excerpt: data.excerpt || '', content: lexicalAST as any, ...(featuredImageId ? { featuredImage: featuredImageId } : {}), }, }); console.log(`✅ Created ${slug}`); } } catch (err: any) { console.error(`❌ Failed ${slug}`, err.message); } } } console.log('Migration completed.'); process.exit(0); } migrateBlogPages().catch(console.error);