()
| 13 | const OUTPUT_DIR = path.resolve(import.meta.dirname, '../server/public/images/stories'); |
| 14 | |
| 15 | async function main() { |
| 16 | const pool = getPool(); |
| 17 | |
| 18 | const { rows } = await pool.query( |
| 19 | `SELECT slug, title, category, excerpt, featured_image_url |
| 20 | FROM perspectives |
| 21 | WHERE status = 'published' |
| 22 | ORDER BY published_at DESC` |
| 23 | ); |
| 24 | |
| 25 | // Filter to those without a cover image file on disk (.png or .jpg) |
| 26 | const missing = rows.filter((p: any) => { |
| 27 | const base = path.join(OUTPUT_DIR, `cover-${p.slug}`); |
| 28 | return !fs.existsSync(`${base}.png`) && !fs.existsSync(`${base}.jpg`); |
| 29 | }); |
| 30 | |
| 31 | if (missing.length === 0) { |
| 32 | console.log('All perspectives have cover images.'); |
| 33 | process.exit(0); |
| 34 | } |
| 35 | |
| 36 | console.log(`${missing.length} perspectives need cover images:\n`); |
| 37 | for (const p of missing) { |
| 38 | console.log(` - ${p.slug}: ${p.title}`); |
| 39 | } |
| 40 | console.log(); |
| 41 | |
| 42 | for (const p of missing) { |
| 43 | const outPath = path.join(OUTPUT_DIR, `cover-${p.slug}.png`); |
| 44 | console.log(`Generating: ${p.slug}...`); |
| 45 | try { |
| 46 | const { imageBuffer } = await generateIllustration({ |
| 47 | title: p.title, |
| 48 | category: p.category || undefined, |
| 49 | excerpt: p.excerpt || undefined, |
| 50 | }); |
| 51 | fs.writeFileSync(outPath, imageBuffer); |
| 52 | console.log(` Saved: ${outPath} (${Math.round(imageBuffer.length / 1024)} KB)`); |
| 53 | } catch (e: any) { |
| 54 | console.log(` ERROR: ${e.message.slice(0, 200)}`); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | process.exit(0); |
| 59 | } |
| 60 | main(); |
no test coverage detected