#!/usr/bin/env ts-node /** * Excel Datasheet Generator * * Generates per-product .xlsx datasheets using ONLY data from Payload CMS. * No external Excel files required. */ import * as fs from 'fs'; import * as path from 'path'; import * as XLSX from 'xlsx'; import { getPayload } from 'payload'; import configPromise from '@payload-config'; import { buildExcelModel, ProductData as ExcelProductData } from './lib/excel-data-parser'; const CONFIG = { outputDir: path.join(process.cwd(), 'public/datasheets'), } as const; // ─── Types ────────────────────────────────────────────────────────────────────── interface ProductData { title: string; slug: string; sku: string; locale: string; categories: string[]; description: string; technicalItems: Array<{ label: string; value: string; unit?: string }>; voltageTables: Array<{ voltageLabel: string; metaItems: Array<{ label: string; value: string; unit?: string }>; columns: Array<{ key: string; label: string; get: (rowIndex: number) => string }>; crossSections: string[]; }>; } // ─── Helpers ──────────────────────────────────────────────────────────────────── function stripHtml(html: string): string { if (!html) return ''; return html.replace(/<[^>]*>/g, '').trim(); } function ensureOutputDir(): void { if (!fs.existsSync(CONFIG.outputDir)) { fs.mkdirSync(CONFIG.outputDir, { recursive: true }); } } // ─── CMS Product Loading ──────────────────────────────────────────────────────── async function fetchProductsFromCMS(locale: 'en' | 'de'): Promise { const products: ProductData[] = []; try { const payload = await getPayload({ config: configPromise }); const isDev = process.env.NODE_ENV === 'development'; const result = await payload.find({ collection: 'products', where: { ...(!isDev ? { _status: { equals: 'published' } } : {}), }, locale: locale as any, pagination: false, }); for (const doc of result.docs) { if (!doc.title || !doc.slug) continue; const excelProductData: ExcelProductData = { name: String(doc.title), slug: String(doc.slug), sku: String(doc.sku || ''), locale, }; const parsedModel = buildExcelModel({ product: excelProductData, locale }); products.push({ title: String(doc.title), slug: String(doc.slug), sku: String(doc.sku || ''), locale, categories: Array.isArray(doc.categories) ? doc.categories.map((c: any) => String(c.category || c)).filter(Boolean) : [], description: stripHtml(String(doc.description || '')), technicalItems: parsedModel.ok ? parsedModel.technicalItems : [], voltageTables: parsedModel.ok ? parsedModel.voltageTables : [], }); } } catch (error) { console.error(`[Payload] Failed to fetch products (${locale}):`, error); } return products; } // ─── Excel Generation ─────────────────────────────────────────────────────────── function generateExcelForProduct(product: ProductData): Buffer { const workbook = XLSX.utils.book_new(); const l = product.locale === 'de'; // ── Sheet 1: Product Info ── const infoData: Array<[string, string]> = [ [l ? 'Produktname' : 'Product Name', product.title], [l ? 'Artikelnummer' : 'SKU', product.sku], [l ? 'Kategorie' : 'Category', product.categories.join(', ') || '-'], [l ? 'Beschreibung' : 'Description', product.description || '-'], ]; const infoSheet = XLSX.utils.aoa_to_sheet(infoData); infoSheet['!cols'] = [{ wch: 25 }, { wch: 65 }]; XLSX.utils.book_append_sheet(workbook, infoSheet, l ? 'Produktinfo' : 'Product Info'); // ── Sheet 2: Technical Data ── if (product.technicalItems.length > 0) { const techData: Array<[string, string]> = product.technicalItems.map(item => { const label = item.unit ? `${item.label} [${item.unit}]` : item.label; return [label, item.value]; }); const techSheet = XLSX.utils.aoa_to_sheet([ [l ? 'Eigenschaft' : 'Property', l ? 'Wert' : 'Value'], ...techData ]); techSheet['!cols'] = [{ wch: 40 }, { wch: 60 }]; XLSX.utils.book_append_sheet(workbook, techSheet, l ? 'Technische Daten' : 'Technical Data'); } // ── Sheet 3+: Voltage Tables ── for (const table of product.voltageTables) { const headers = ['Configuration/Cross-section', ...table.columns.map(c => c.label)]; const dataRows = table.crossSections.map((cs, rowIndex) => { return [cs, ...table.columns.map(c => c.get(rowIndex) || '-')]; }); const ws = XLSX.utils.aoa_to_sheet([headers, ...dataRows]); ws['!cols'] = headers.map(() => ({ wch: 22 })); const safeName = table.voltageLabel.replace(/[:\\/?*[\]]/g, '-').trim(); const sheetName = safeName.substring(0, 31); XLSX.utils.book_append_sheet(workbook, ws, sheetName); } const buffer = XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' }); return Buffer.from(buffer); } // ─── Main ─────────────────────────────────────────────────────────────────────── async function main(): Promise { const start = Date.now(); console.log('Starting Excel datasheet generation (Legacy Excel Source)'); ensureOutputDir(); const locales: Array<'en' | 'de'> = ['en', 'de']; let generated = 0; for (const locale of locales) { console.log(`\n[${locale.toUpperCase()}] Fetching products...`); const products = await fetchProductsFromCMS(locale); console.log(`Found ${products.length} products.`); for (const product of products) { try { const buffer = generateExcelForProduct(product); const fileName = `${product.slug}-${locale}.xlsx`; const subfolder = path.join(CONFIG.outputDir, 'products'); if (!fs.existsSync(subfolder)) fs.mkdirSync(subfolder, { recursive: true }); fs.writeFileSync(path.join(subfolder, fileName), buffer); console.log(`✓ Generated: ${fileName}`); generated++; } catch (error) { console.error(`✗ Failed for ${product.title}:`, error); } } } console.log(`\n✅ Done! Generated ${generated} files.`); console.log(`Output: ${CONFIG.outputDir}`); console.log(`Time: ${((Date.now() - start) / 1000).toFixed(2)}s`); } main().catch(console.error);