128 lines
4.3 KiB
TypeScript
128 lines
4.3 KiB
TypeScript
#!/usr/bin/env ts-node
|
|
/**
|
|
* PDF Datasheet Generator (React-PDF)
|
|
*
|
|
* Uses the same Excel-driven data model as the legacy generator, but renders
|
|
* PDFs via `@react-pdf/renderer` for maintainable layout and pagination.
|
|
*/
|
|
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
import type { ProductData } from './pdf/model/types';
|
|
import { generateDatasheetPdfBuffer } from './pdf/react-pdf/generate-datasheet-pdf';
|
|
import { generateFileName, normalizeValue } from './pdf/model/utils';
|
|
|
|
const CONFIG = {
|
|
productsFile: path.join(process.cwd(), 'data/processed/products.json'),
|
|
outputDir: path.join(process.cwd(), 'public/datasheets'),
|
|
chunkSize: 10,
|
|
} as const;
|
|
|
|
function ensureOutputDir(): void {
|
|
if (!fs.existsSync(CONFIG.outputDir)) {
|
|
fs.mkdirSync(CONFIG.outputDir, { recursive: true });
|
|
}
|
|
}
|
|
|
|
async function readProductsStream(): Promise<ProductData[]> {
|
|
console.log('Reading products.json...');
|
|
return new Promise((resolve, reject) => {
|
|
const stream = fs.createReadStream(CONFIG.productsFile, { encoding: 'utf8' });
|
|
let data = '';
|
|
stream.on('data', chunk => {
|
|
data += chunk;
|
|
});
|
|
stream.on('end', () => {
|
|
try {
|
|
const products = JSON.parse(data) as ProductData[];
|
|
console.log(`Loaded ${products.length} products`);
|
|
resolve(products);
|
|
} catch (error) {
|
|
reject(new Error(`Failed to parse JSON: ${error}`));
|
|
}
|
|
});
|
|
stream.on('error', error => reject(new Error(`Failed to read file: ${error}`)));
|
|
});
|
|
}
|
|
|
|
async function processChunk(products: ProductData[], chunkIndex: number, totalChunks: number): Promise<void> {
|
|
console.log(`\nProcessing chunk ${chunkIndex + 1}/${totalChunks} (${products.length} products)...`);
|
|
|
|
for (const product of products) {
|
|
try {
|
|
const locale = (product.locale || 'en') as 'en' | 'de';
|
|
const buffer = await generateDatasheetPdfBuffer({ product, locale });
|
|
const fileName = generateFileName(product, locale);
|
|
fs.writeFileSync(path.join(CONFIG.outputDir, fileName), buffer);
|
|
console.log(`✓ ${locale.toUpperCase()}: ${fileName}`);
|
|
await new Promise(resolve => setTimeout(resolve, 25));
|
|
} catch (error) {
|
|
console.error(`✗ Failed to process product ${product.id}:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function processProductsInChunks(): Promise<void> {
|
|
console.log('Starting PDF generation (React-PDF)');
|
|
ensureOutputDir();
|
|
|
|
const allProducts = await readProductsStream();
|
|
if (allProducts.length === 0) {
|
|
console.log('No products found');
|
|
return;
|
|
}
|
|
|
|
// Dev convenience: generate only one locale / one product subset.
|
|
// IMPORTANT: apply filters BEFORE PDF_LIMIT so the limit works within the filtered set.
|
|
let products = allProducts;
|
|
|
|
const onlyLocale = normalizeValue(String(process.env.PDF_LOCALE || '')).toLowerCase();
|
|
if (onlyLocale === 'de' || onlyLocale === 'en') {
|
|
products = products.filter(p => (p.locale || 'en') === onlyLocale);
|
|
}
|
|
|
|
const match = normalizeValue(String(process.env.PDF_MATCH || '')).toLowerCase();
|
|
if (match) {
|
|
products = products.filter(p => {
|
|
const hay = [p.slug, p.translationKey, p.sku, p.name]
|
|
.filter(Boolean)
|
|
.join(' ')
|
|
.toLowerCase();
|
|
return hay.includes(match);
|
|
});
|
|
}
|
|
|
|
const limit = Number(process.env.PDF_LIMIT || '0');
|
|
products = Number.isFinite(limit) && limit > 0 ? products.slice(0, limit) : products;
|
|
|
|
const enProducts = products.filter(p => (p.locale || 'en') === 'en');
|
|
const deProducts = products.filter(p => (p.locale || 'en') === 'de');
|
|
console.log(`Found ${enProducts.length} EN + ${deProducts.length} DE products`);
|
|
|
|
const totalChunks = Math.ceil(products.length / CONFIG.chunkSize);
|
|
for (let i = 0; i < totalChunks; i++) {
|
|
const chunk = products.slice(i * CONFIG.chunkSize, (i + 1) * CONFIG.chunkSize);
|
|
await processChunk(chunk, i, totalChunks);
|
|
}
|
|
|
|
console.log('\n✅ PDF generation completed!');
|
|
console.log(`Generated ${enProducts.length} EN + ${deProducts.length} DE PDFs`);
|
|
console.log(`Output: ${CONFIG.outputDir}`);
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const start = Date.now();
|
|
try {
|
|
await processProductsInChunks();
|
|
console.log(`\nTime: ${((Date.now() - start) / 1000).toFixed(2)}s`);
|
|
} catch (error) {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(console.error);
|
|
|
|
export { main as generatePDFDatasheets };
|