// Render the corporate identity band used by the delivery package. // // The band is the same assets/header.html the PDF is printed with, captured as // an image so its ribbon geometry survives into Word, where it is placed in a // real page header instead of being reconstructed in the body. import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { chromium } from 'playwright'; const here = path.dirname(fileURLToPath(import.meta.url)); const root = path.resolve(here, '../..'); const assets = path.join(here, 'assets'); export const BAND_MM = { width: 210, height: 27 }; const SCALE = 3; // the band is drawn at 3x so it stays crisp in print const applyTemplate = (template, values) => template.replace(/{{(\w+)}}/g, (_, key) => values[key] ?? ''); const slug = (title) => title.replace(/[^\w-]+/g, '-').replace(/^-|-$/g, '').toLowerCase() || 'band'; async function markdownFiles(directory) { const entries = await fs.readdir(directory, { withFileTypes: true }); const results = await Promise.all(entries.map(async (entry) => { const target = path.join(directory, entry.name); if (entry.isDirectory()) return markdownFiles(target); return entry.isFile() && entry.name.endsWith('.md') ? [target] : []; })); return results.flat(); } async function main() { const [sourceRoot, outputRoot] = process.argv.slice(2); if (!sourceRoot || !outputRoot) throw new Error('Usage: render-ci-band.mjs SOURCE_DIRECTORY OUTPUT_DIRECTORY'); const [template, logo] = await Promise.all([ fs.readFile(path.join(assets, 'header.html'), 'utf8'), fs.readFile(path.join(root, 'app/assets/images/logo.svg')) ]); const values = { logoPath: `data:image/svg+xml;base64,${logo.toString('base64')}`, ciBlue: '#0797d5', ciGrey: '#8e8e8e' // ribbon grey measured from the audited reference documents }; await fs.mkdir(outputRoot, { recursive: true }); const sources = (await markdownFiles(sourceRoot)).sort(); const bands = {}; const rendered = new Map(); const browser = await chromium.launch({ headless: true }); try { const page = await browser.newPage({ deviceScaleFactor: SCALE }); for (const source of sources) { const markdown = await fs.readFile(source, 'utf8'); const title = markdown.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? 'BRN WMS'; const footer = markdown.match(/^\s*$/m)?.[1] ?? ''; if (!rendered.has(title)) { const file = path.join(outputRoot, `${slug(title)}.png`); await page.setContent( '' + `
` + `${applyTemplate(template, { ...values, documentTitle: title })}
`, { waitUntil: 'networkidle' } ); await page.locator('#band').screenshot({ type: 'png', path: file }); rendered.set(title, file); } bands[path.relative(sourceRoot, source)] = { title, footer, band: rendered.get(title) }; } } finally { await browser.close(); } await fs.writeFile(path.join(outputRoot, 'bands.json'), `${JSON.stringify(bands, null, 2)}\n`); console.log(`${rendered.size} band${rendered.size === 1 ? '' : 's'} rendered for ${sources.length} documents`); } main().catch((error) => { console.error(error); process.exit(1); });