Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 8s
Build & Deploy / 🧪 QA (push) Failing after 1m13s
Build & Deploy / 🏗️ Build (push) Failing after 5m53s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Smoke Test (push) Has been skipped
Build & Deploy / ⚡ Lighthouse (push) Has been skipped
Build & Deploy / ♿ WCAG (push) Has been skipped
Build & Deploy / 🛡️ Quality Gates (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 4s
154 lines
4.8 KiB
TypeScript
154 lines
4.8 KiB
TypeScript
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<string | null> {
|
|
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);
|