Gas. Ini upgrade yang memang wajib.
Sekarang upload-r2.js kita bikin lebih pintar:
- cek object di R2 dulu
- kalau file sudah ada → skip
- kalau belum ada → upload
Jadi publish berikutnya jauh lebih cepat.
Replace upload-r2.js dengan versi ini
import "dotenv/config";
import fs from "fs";
import path from "path";
import {
S3Client,
PutObjectCommand,
HeadObjectCommand
} from "@aws-sdk/client-s3";
const mediaDir = "./media";
const {
R2_ACCOUNT_ID,
R2_ACCESS_KEY_ID,
R2_SECRET_ACCESS_KEY,
R2_BUCKET
} = process.env;
if (!R2_ACCOUNT_ID || !R2_ACCESS_KEY_ID || !R2_SECRET_ACCESS_KEY || !R2_BUCKET) {
console.error("Env R2 belum lengkap. Cek .env");
process.exit(1);
}
const client = new S3Client({
region: "auto",
endpoint: `https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: R2_ACCESS_KEY_ID,
secretAccessKey: R2_SECRET_ACCESS_KEY
}
});
function getContentType(fileName) {
const ext = path.extname(fileName).toLowerCase();
if (ext === ".mp4") return "video/mp4";
if (ext === ".m3u8") return "application/vnd.apple.mpegurl";
if (ext === ".ts") return "video/mp2t";
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
if (ext === ".png") return "image/png";
if (ext === ".webp") return "image/webp";
if (ext === ".gif") return "image/gif";
if (ext === ".json") return "application/json";
return "application/octet-stream";
}
async function objectExists(key) {
try {
await client.send(
new HeadObjectCommand({
Bucket: R2_BUCKET,
Key: key
})
);
return true;
} catch (err) {
const code = err?.name || err?.Code || err?.code;
const status = err?.$metadata?.httpStatusCode;
if (
code === "NotFound" ||
code === "NoSuchKey" ||
status === 404
) {
return false;
}
throw err;
}
}
async function uploadFile(fileName) {
const fullPath = path.join(mediaDir, fileName);
if (!fs.statSync(fullPath).isFile()) return;
const exists = await objectExists(fileName);
if (exists) {
console.log(`Skip (exists): ${fileName}`);
return;
}
const body = fs.readFileSync(fullPath);
const command = new PutObjectCommand({
Bucket: R2_BUCKET,
Key: fileName,
Body: body,
ContentType: getContentType(fileName)
});
await client.send(command);
console.log(`Uploaded: ${fileName}`);
}
async function main() {
const files = fs.readdirSync(mediaDir);
for (const file of files) {
await uploadFile(file);
}
console.log("R2 upload selesai.");
}
main().catch(err => {
console.error("Upload gagal:", err);
process.exit(1);
});
Cara test
Jalankan:
npm run r2
Kalau file sudah pernah diupload, harus muncul seperti:
Skip (exists): ade_manisss02_2022-05-10_10-01-23.mp4
Skip (exists): ade_manisss02_2022-05-10_10-01-23.jpg
Kalau file baru, harus muncul:
Uploaded: namafilebaru.mp4
Penting
Versi ini skip berdasarkan:
- nama file / key
- bukan ukuran
- bukan checksum
Artinya kalau kamu ganti isi file tapi nama file tetap sama, script akan anggap file itu sudah ada dan di-skip.
Jadi sementara aturannya:
Kalau isi file berubah, ubah nama file juga.
Contoh:
video1.mp4
video1-v2.mp4
Itu aman.
Kalau kamu mau lebih pintar nanti
Kita bisa upgrade lagi jadi:
- cek size lokal vs size object R2
- atau pakai hash/checksum
Tapi sekarang ini sudah cukup bagus dan simpel.
Step berikutnya yang paling pas
Setelah skip upload beres, upgrade terbaik adalah:
auto release satu command penuh
jadi cukup:
npm run release -- "add new videos"
dan semuanya jalan sekaligus.
Kalau mau, next saya bikinkan release.js yang sekalian:
- upload R2
- generate data
- git add
- git commit
- git push