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); 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 migrateBlogPosts() { console.log('[Blog Migration] 🔍 Using POSTGRES_URI:', process.env.POSTGRES_URI || 'NOT SET'); console.log('[Blog Migration] 🔍 Using DATABASE_URI:', process.env.DATABASE_URI || 'NOT SET'); let payload; try { payload = await getPayload({ config: configPromise }); } catch (err: any) { console.error('[Blog Migration] ❌ Failed to initialize Payload:', err); process.exit(1); } const locales = ['en', 'de']; for (const locale of locales) { const postsDir = path.join(process.cwd(), 'data', 'blog', locale); if (!fs.existsSync(postsDir)) continue; const files = fs.readdirSync(postsDir); for (const file of files) { if (!file.endsWith('.mdx')) continue; const slug = file.replace(/\.mdx$/, ''); const filePath = path.join(postsDir, 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 publishDate = data.date ? new Date(data.date).toISOString() : new Date().toISOString(); 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: 'posts', 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: 'posts', id: existing.docs[0].id, data: { content: lexicalAST as any, _status: status as any, ...(featuredImageId ? { featuredImage: featuredImageId } : {}), }, }); console.log(`✅ AST Components & Image RE-INJECTED for ${slug}`); } else { await payload.create({ collection: 'posts', data: { title: data.title, slug: slug, locale: locale, date: publishDate, category: data.category || '', excerpt: data.excerpt || '', content: lexicalAST as any, _status: status 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); } migrateBlogPosts().catch(console.error);