/** * Update images — jasonwoltje.com * Uploads new photos and assigns them to globals/posts. * Idempotent by alt-text match. */ import { getPayload } from 'payload' import config from '@payload-config' import fs from 'node:fs' import path from 'node:path' async function main() { const payload = await getPayload({ config }) const imagesDir = path.resolve(process.cwd(), 'images') const uploads: Array<{ file: string; alt: string; key: string }> = [ { file: 'gpt-image-1.5_creative_tech_founder_portrait_man_with_bald_head_and_brown-ginger_full_beard_bl-0.jpg', alt: 'Jason Woltje — tech founder portrait, dark background', key: 'hero', }, { file: 'gpt-image-1.5_bold_social_media_profile_portrait_man_with_bald_head_and_brown-ginger_full_bear-0.jpg', alt: 'Jason Woltje — social media profile, neon gradient', key: 'og', }, { file: 'gpt-image-1.5_documentary_style_environmental_portrait_man_with_bald_head_and_brown-ginger_ful-0.jpg', alt: 'Jason Woltje — at the desk, documentary style', key: 'desk', }, { file: 'gpt-image-1.5_illustrated_portrait_stylized_modern_digital_art_style_man_with_bald_head_and_br-0.jpg', alt: 'Jason Woltje — illustrated portrait', key: 'illustration', }, { file: 'gpt-image-1.5_authoritative_thought_leader_portrait_man_with_bald_head_and_brown-ginger_full_b-0.jpg', alt: 'Jason Woltje — thought leader portrait, city backdrop', key: 'thought-leader', }, { file: 'gpt-image-1.5_high-end_fashion_forward_portrait_man_with_bald_head_and_brown-ginger_full_beard-0.jpg', alt: 'Jason Woltje — fashion editorial portrait', key: 'editorial', }, ] const ids: Record = {} console.log('\n── Uploading new images ──────────────────────────────────') for (const u of uploads) { const filePath = path.join(imagesDir, u.file) if (!fs.existsSync(filePath)) { console.log(` ⚠ Missing: ${u.file}`) continue } const existing = await payload.find({ collection: 'media', where: { alt: { equals: u.alt } }, limit: 1, }) if (existing.totalDocs > 0) { ids[u.key] = existing.docs[0]!.id as number console.log(` ↷ Already exists: ${u.alt} (id=${ids[u.key]})`) continue } // Update alt of old upload if it used a generic name (from initial seed) const doc = await payload.create({ collection: 'media', filePath, data: { alt: u.alt }, }) ids[u.key] = doc.id as number console.log(` ✓ Uploaded: ${u.alt} (id=${doc.id})`) } // ── Update Home hero ────────────────────────────────────────────────── if (ids['hero']) { console.log('\n── Updating Home hero image ─────────────────────────────') const home = await payload.findGlobal({ slug: 'home', depth: 0 }) const heroData = (home as Record).hero as Record | undefined await payload.updateGlobal({ slug: 'home', data: { hero: { ...(heroData ?? {}), heroImage: ids['hero'], }, }, }) console.log(` ✓ Home heroImage → id=${ids['hero']}`) } // ── Update SEO OG image ─────────────────────────────────────────────── if (ids['og']) { console.log('\n── Updating SEO defaultOgImage ──────────────────────────') await payload.updateGlobal({ slug: 'seo', data: { defaultOgImage: ids['og'] }, }) console.log(` ✓ SEO defaultOgImage → id=${ids['og']}`) } // ── Update post cover images ────────────────────────────────────────── console.log('\n── Updating post cover images ───────────────────────────') const postCovers: Array<{ slugContains: string; imageKey: string }> = [ { slugContains: 'cascading-traefik', imageKey: 'desk' }, { slugContains: 'migrating-from-it', imageKey: 'thought-leader' }, { slugContains: 'payload-3', imageKey: 'illustration' }, ] for (const pc of postCovers) { const imageId = ids[pc.imageKey] if (!imageId) continue const { docs } = await payload.find({ collection: 'posts', where: { slug: { contains: pc.slugContains } }, limit: 1, depth: 0, }) if (docs.length === 0) { console.log(` ⚠ Post not found: slug contains "${pc.slugContains}"`) continue } const post = docs[0]! await payload.update({ collection: 'posts', id: post.id, data: { coverImage: imageId }, }) console.log(` ✓ Post "${post.slug}" coverImage → id=${imageId}`) } // ── Assign project hero images ──────────────────────────────────────── console.log('\n── Updating project hero images ─────────────────────────') const projectHeroes: Array<{ slugContains: string; imageKey: string }> = [ { slugContains: 'mosaic-stack', imageKey: 'hero' }, { slugContains: 'jasonwoltje', imageKey: 'editorial' }, ] for (const ph of projectHeroes) { const imageId = ids[ph.imageKey] if (!imageId) continue const { docs } = await payload.find({ collection: 'projects', where: { slug: { contains: ph.slugContains } }, limit: 1, depth: 0, }) if (docs.length === 0) { console.log(` ⚠ Project not found: slug contains "${ph.slugContains}"`) continue } const project = docs[0]! await payload.update({ collection: 'projects', id: project.id, data: { heroImage: imageId }, }) console.log(` ✓ Project "${project.slug}" heroImage → id=${imageId}`) } console.log('\n════════════════════════════════════════════════════════') console.log(' IMAGE UPDATE COMPLETE') console.log('════════════════════════════════════════════════════════\n') process.exit(0) } main().catch((err) => { console.error('Fatal:', err) process.exit(1) })