#!/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'; // Single sheet: all cross-sections from all voltage tables combined const hasMultipleVoltages = product.voltageTables.length > 1; const allRows: string[][] = []; // Build unified header row // Use columns from the first voltage table (they're the same across tables) const refTable = product.voltageTables[0]; if (!refTable) { // No voltage tables — create a minimal info sheet const ws = XLSX.utils.aoa_to_sheet([ [product.title], [l ? 'Keine Querschnittsdaten verfügbar' : 'No cross-section data available'], ]); ws['!cols'] = [{ wch: 40 }]; XLSX.utils.book_append_sheet(workbook, ws, product.title.substring(0, 31)); return Buffer.from(XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' })); } const headers: string[] = [ l ? 'Querschnitt' : 'Cross-section', ...(hasMultipleVoltages ? [l ? 'Spannung' : 'Voltage'] : []), ...refTable.columns.map(c => c.label), ]; allRows.push(headers); // Merge rows from all voltage tables for (const table of product.voltageTables) { for (let rowIdx = 0; rowIdx < table.crossSections.length; rowIdx++) { const row: string[] = [ table.crossSections[rowIdx], ...(hasMultipleVoltages ? [table.voltageLabel] : []), ...table.columns.map(c => c.get(rowIdx) || '-'), ]; allRows.push(row); } } const ws = XLSX.utils.aoa_to_sheet(allRows); // Auto-width: first col wider for cross-section labels ws['!cols'] = headers.map((_, i) => ({ wch: i === 0 ? 30 : 18 })); const sheetName = product.title.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);