Some checks failed
Deploy to CF Multisite / deploy (push) Failing after 1m53s
- Cloudflare Workers + R2 기반 - Edge 캐싱으로 비용 절감 - 티어별 Rate Limiting (free/basic/pro) - KV 기반 사용량 추적 - Admin API (usage, customers, tiers, stats) - Gitea Actions 배포 워크플로우 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
146 lines
3.7 KiB
JavaScript
146 lines
3.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* R2 업로드 스크립트
|
|
*
|
|
* 사용법:
|
|
* node scripts/upload.js <customer-id> <source-dir>
|
|
*
|
|
* 예시:
|
|
* node scripts/upload.js demo-site ./sample-site
|
|
*
|
|
* 환경변수:
|
|
* R2_ENDPOINT - R2 엔드포인트 URL
|
|
* R2_ACCESS_KEY - R2 접근 키
|
|
* R2_SECRET_KEY - R2 시크릿 키
|
|
* R2_BUCKET - 버킷 이름 (기본: multisite-bucket)
|
|
*/
|
|
|
|
const { S3Client, PutObjectCommand, ListObjectsV2Command, DeleteObjectsCommand } = require('@aws-sdk/client-s3');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// MIME 타입 매핑
|
|
const MIME_TYPES = {
|
|
'.html': 'text/html; charset=utf-8',
|
|
'.css': 'text/css',
|
|
'.js': 'application/javascript',
|
|
'.json': 'application/json',
|
|
'.png': 'image/png',
|
|
'.jpg': 'image/jpeg',
|
|
'.jpeg': 'image/jpeg',
|
|
'.gif': 'image/gif',
|
|
'.svg': 'image/svg+xml',
|
|
'.ico': 'image/x-icon',
|
|
'.woff': 'font/woff',
|
|
'.woff2': 'font/woff2',
|
|
'.ttf': 'font/ttf',
|
|
'.pdf': 'application/pdf',
|
|
'.xml': 'application/xml',
|
|
'.txt': 'text/plain',
|
|
'.md': 'text/markdown',
|
|
};
|
|
|
|
// 재귀적으로 모든 파일 찾기
|
|
function getAllFiles(dir, files = []) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) {
|
|
getAllFiles(fullPath, files);
|
|
} else {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
async function main() {
|
|
const [,, customerId, sourceDir] = process.argv;
|
|
|
|
if (!customerId || !sourceDir) {
|
|
console.error('Usage: node scripts/upload.js <customer-id> <source-dir>');
|
|
console.error('Example: node scripts/upload.js demo-site ./sample-site');
|
|
process.exit(1);
|
|
}
|
|
|
|
// 환경변수 체크
|
|
const endpoint = process.env.R2_ENDPOINT;
|
|
const accessKeyId = process.env.R2_ACCESS_KEY;
|
|
const secretAccessKey = process.env.R2_SECRET_KEY;
|
|
const bucket = process.env.R2_BUCKET || 'multisite-bucket';
|
|
|
|
if (!endpoint || !accessKeyId || !secretAccessKey) {
|
|
console.error('Missing environment variables:');
|
|
console.error(' R2_ENDPOINT, R2_ACCESS_KEY, R2_SECRET_KEY');
|
|
console.error('');
|
|
console.error('R2 API 토큰 생성: Cloudflare Dashboard > R2 > Manage R2 API Tokens');
|
|
process.exit(1);
|
|
}
|
|
|
|
// S3 클라이언트 생성 (R2는 S3 호환)
|
|
const s3 = new S3Client({
|
|
region: 'auto',
|
|
endpoint,
|
|
credentials: { accessKeyId, secretAccessKey },
|
|
});
|
|
|
|
const sourcePath = path.resolve(sourceDir);
|
|
|
|
if (!fs.existsSync(sourcePath)) {
|
|
console.error(`Source directory not found: ${sourcePath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log(`Uploading to R2...`);
|
|
console.log(` Customer: ${customerId}`);
|
|
console.log(` Source: ${sourcePath}`);
|
|
console.log(` Bucket: ${bucket}`);
|
|
console.log('');
|
|
|
|
// 모든 파일 수집
|
|
const files = getAllFiles(sourcePath);
|
|
const prefix = `sites/${customerId}`;
|
|
|
|
// 업로드
|
|
let uploaded = 0;
|
|
let failed = 0;
|
|
|
|
for (const file of files) {
|
|
const relativePath = path.relative(sourcePath, file);
|
|
const key = `${prefix}/${relativePath}`.replace(/\\/g, '/');
|
|
const ext = path.extname(file).toLowerCase();
|
|
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
|
|
|
|
try {
|
|
const body = fs.readFileSync(file);
|
|
|
|
await s3.send(new PutObjectCommand({
|
|
Bucket: bucket,
|
|
Key: key,
|
|
Body: body,
|
|
ContentType: contentType,
|
|
}));
|
|
|
|
console.log(` ✓ ${key}`);
|
|
uploaded++;
|
|
} catch (error) {
|
|
console.error(` ✗ ${key}: ${error.message}`);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
console.log('');
|
|
console.log(`Done! Uploaded: ${uploaded}, Failed: ${failed}`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch(error => {
|
|
console.error('Fatal error:', error);
|
|
process.exit(1);
|
|
});
|