import { existsSync, readFileSync } from 'node:fs' import postgres from 'postgres' import { createPostThumbnailForImageUrl, getExistingPostThumbnailUrl, getPostThumbnailUrl } from '../server/utils/post-thumbnail-image.js' /** * 로컬 실행용 환경 파일을 process.env에 반영한다. * @returns {void} */ const loadEnvironmentFile = () => { const candidates = [ process.env.ENV_FILE, '.env.development', '.env' ].filter(Boolean) const envFile = candidates.find((filePath) => existsSync(filePath)) if (!envFile) { return } const lines = readFileSync(envFile, 'utf8').split(/\r?\n/) for (const line of lines) { const trimmedLine = line.trim() if (!trimmedLine || trimmedLine.startsWith('#')) { continue } const separatorIndex = trimmedLine.indexOf('=') if (separatorIndex === -1) { continue } const key = trimmedLine.slice(0, separatorIndex).trim() const rawValue = trimmedLine.slice(separatorIndex + 1).trim() if (!key || process.env[key] !== undefined) { continue } process.env[key] = rawValue.replace(/^['"]|['"]$/g, '') } } /** * 게시물 대표 이미지 목록을 조회한다. * @returns {Promise>} 대표 이미지 목록 */ const listPostFeaturedImages = async () => { loadEnvironmentFile() if (!process.env.DATABASE_URL) { throw new Error('DATABASE_URL 환경 변수가 필요합니다.') } const sql = postgres(process.env.DATABASE_URL, { max: 1, idle_timeout: 5, connect_timeout: 10 }) try { const rows = await sql` SELECT featured_image FROM posts WHERE featured_image IS NOT NULL ORDER BY COALESCE(published_at, updated_at) DESC ` return rows.map((row) => ({ featuredImage: row.featured_image })) } finally { await sql.end({ timeout: 5 }) } } const posts = await listPostFeaturedImages() const featuredImageUrls = [...new Set(posts .map((post) => post.featuredImage) .filter((url) => getPostThumbnailUrl(url)))] let createdCount = 0 let skippedCount = 0 for (const imageUrl of featuredImageUrls) { if (getExistingPostThumbnailUrl(imageUrl)) { skippedCount += 1 continue } const thumbnailUrl = await createPostThumbnailForImageUrl(imageUrl) if (thumbnailUrl) { createdCount += 1 } else { skippedCount += 1 } } process.stdout.write(`게시물 대표 이미지 카드 썸네일 생성 완료: 생성 ${createdCount}개, 건너뜀 ${skippedCount}개\n`)