Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 7s
Build & Deploy / 🧪 QA (push) Failing after 2m15s
Build & Deploy / 🏗️ Build (push) Has been skipped
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🧪 Post-Deploy Verification (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 1s
191 lines
7.4 KiB
TypeScript
191 lines
7.4 KiB
TypeScript
#!/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<ProductData[]> {
|
|
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<void> {
|
|
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);
|