import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3"; import * as fs from "fs"; import * as path from "path"; const S3_ENDPOINT = process.env.S3_ENDPOINT; const S3_REGION = process.env.S3_REGION || "fsn1"; const S3_BUCKET = process.env.S3_BUCKET; const S3_PREFIX = process.env.S3_PREFIX; const S3_ACCESS_KEY = process.env.S3_ACCESS_KEY; const S3_SECRET_KEY = process.env.S3_SECRET_KEY; if (!S3_ENDPOINT || !S3_BUCKET || !S3_ACCESS_KEY || !S3_SECRET_KEY) { console.error("Missing S3 credentials in environment"); process.exit(1); } const s3Client = new S3Client({ region: S3_REGION, endpoint: S3_ENDPOINT, credentials: { accessKeyId: S3_ACCESS_KEY, secretAccessKey: S3_SECRET_KEY, }, forcePathStyle: true, }); async function uploadDirectory(dirPath: string, prefix: string) { const files = fs.readdirSync(dirPath, { withFileTypes: true }); for (const file of files) { if (file.name === ".DS_Store" || file.name === ".gitkeep") continue; const fullPath = path.join(dirPath, file.name); // Combine prefix with filename, ensuring no double slashes, e.g., mb-grid-solutions/media/filename.ext const s3Key = `${prefix}/${file.name}`.replace(/\/+/g, "/"); if (file.isDirectory()) { await uploadDirectory(fullPath, s3Key); } else { const fileContent = fs.readFileSync(fullPath); let contentType = "application/octet-stream"; if (file.name.endsWith(".png")) contentType = "image/png"; else if (file.name.endsWith(".jpg") || file.name.endsWith(".jpeg")) contentType = "image/jpeg"; else if (file.name.endsWith(".svg")) contentType = "image/svg+xml"; else if (file.name.endsWith(".webp")) contentType = "image/webp"; else if (file.name.endsWith(".pdf")) contentType = "application/pdf"; try { await s3Client.send( new PutObjectCommand({ Bucket: S3_BUCKET, Key: s3Key, Body: fileContent, ContentType: contentType, ACL: "public-read", // Hetzner requires public-read for public access usually }), ); console.log(`✅ Uploaded ${file.name} to ${S3_BUCKET}/${s3Key}`); } catch (err) { console.error(`❌ Failed to upload ${file.name}:`, err); } } } } async function main() { const mediaDir = path.resolve(process.cwd(), "public/media"); if (fs.existsSync(mediaDir)) { console.log("Uploading public/media..."); // Media inside Payload CMS uses prefix/media usually, like mb-grid-solutions/media await uploadDirectory(mediaDir, `${S3_PREFIX}/media`); } else { console.log("No public/media directory found."); } const assetsDir = path.resolve(process.cwd(), "public/assets"); if (fs.existsSync(assetsDir)) { console.log("Uploading public/assets..."); await uploadDirectory(assetsDir, `${S3_PREFIX}/assets`); } else { console.log("No public/assets directory found."); } } main().catch(console.error);