import { mkdir, readdir, stat } from 'node:fs/promises' import { extname, join } from 'node:path' import sharp from 'sharp' import { POST_THUMBNAIL_HEIGHT, POST_THUMBNAIL_QUALITY, POST_THUMBNAIL_WIDTH, getPostThumbnailDirectoryPath, getPostThumbnailPathForFile, isPostThumbnailSource } from '../server/utils/post-thumbnail-image.js' const uploadRoot = join(process.cwd(), 'public', 'uploads', 'posts') /** * 게시물 업로드 이미지 파일 목록을 수집한다. * @param {string} directoryPath - 탐색 디렉터리 * @returns {Promise} 이미지 파일 경로 목록 */ const collectPostImages = async (directoryPath) => { let entries = [] try { entries = await readdir(directoryPath, { withFileTypes: true }) } catch { return [] } const files = [] for (const entry of entries) { const entryPath = join(directoryPath, entry.name) if (entry.isDirectory()) { if (entry.name === 'thumbs') { continue } files.push(...await collectPostImages(entryPath)) continue } if (entry.isFile() && isPostThumbnailSource('', entry.name)) { files.push(entryPath) } } return files } /** * 게시물 이미지 카드 썸네일을 생성한다. * @param {string} imagePath - 원본 이미지 경로 * @returns {Promise} 새로 생성했는지 여부 */ const createPostThumbnail = async (imagePath) => { const thumbnailPath = getPostThumbnailPathForFile(imagePath) try { await stat(thumbnailPath) return false } catch { await mkdir(getPostThumbnailDirectoryPath(imagePath), { recursive: true }) } await sharp(imagePath) .rotate() .resize({ width: POST_THUMBNAIL_WIDTH, height: POST_THUMBNAIL_HEIGHT, fit: 'cover', position: 'centre', withoutEnlargement: true }) .webp({ quality: POST_THUMBNAIL_QUALITY }) .toFile(thumbnailPath) return true } const images = await collectPostImages(uploadRoot) let createdCount = 0 let skippedCount = 0 for (const imagePath of images) { if (!isPostThumbnailSource('', imagePath) || extname(imagePath).toLowerCase() === '.gif') { skippedCount += 1 continue } if (await createPostThumbnail(imagePath)) { createdCount += 1 } else { skippedCount += 1 } } process.stdout.write(`게시물 카드 썸네일 생성 완료: 생성 ${createdCount}개, 건너뜀 ${skippedCount}개\n`)