miridev-cli
Version:
Official CLI tool for deploying static sites to miri.dev - Deploy your websites in seconds
383 lines (345 loc) • 11.5 kB
JavaScript
const fs = require('fs');
const path = require('path');
const chalk = require('chalk');
// GitHub Actions template variables
const GITHUB_SECRET = '$' + '{{';
const GITHUB_SECRET_END = '}}';
const GITHUB_SECRETS = {
SUPABASE_URL: `${GITHUB_SECRET} secrets.NEXT_PUBLIC_SUPABASE_URL ${GITHUB_SECRET_END}`,
SUPABASE_ANON_KEY: `${GITHUB_SECRET} secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY ${GITHUB_SECRET_END}`,
SUPABASE_SERVICE_ROLE_KEY: `${GITHUB_SECRET} secrets.SUPABASE_SERVICE_ROLE_KEY ${GITHUB_SECRET_END}`,
PAGE_URL: `${GITHUB_SECRET} steps.deployment.outputs.page_url ${GITHUB_SECRET_END}`
};
const templates = {
vercel: {
filename: 'vercel.json',
content: {
version: 2,
name: 'miri-dev-project',
builds: [
{
src: 'package.json',
use: '@vercel/next'
}
],
env: {
NEXT_PUBLIC_SUPABASE_URL: '@supabase_url',
NEXT_PUBLIC_SUPABASE_ANON_KEY: '@supabase_anon_key',
SUPABASE_SERVICE_ROLE_KEY: '@supabase_service_role_key'
},
regions: ['icn1', 'hnd1'],
functions: {
'src/app/api/**/*.ts': {
maxDuration: 30
}
},
headers: [
{
source: '/(.*)',
headers: [
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }
]
}
],
crons: [
{
path: '/api/cron/security-cleanup',
schedule: '0 2 * * *'
}
]
}
},
netlify: {
filename: 'netlify.toml',
content: `[build]
command = "npm run build"
functions = "netlify/functions"
publish = ".next"
[build.environment]
NODE_VERSION = "18"
NPM_VERSION = "9"
[dev]
command = "npm run dev"
port = 3000
targetPort = 3000
autoLaunch = false
[[plugins]]
package = "@netlify/plugin-nextjs"
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
X-XSS-Protection = "1; mode=block"
[[headers]]
for = "/api/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Access-Control-Allow-Methods = "GET, POST, PUT, DELETE, OPTIONS"
Access-Control-Allow-Headers = "Content-Type, Authorization"
[[redirects]]
from = "/docs"
to = "/docs/getting-started"
status = 301
[[headers]]
for = "/_next/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
# 환경 변수는 Netlify 대시보드에서 설정하세요:
# NEXT_PUBLIC_SUPABASE_URL
# NEXT_PUBLIC_SUPABASE_ANON_KEY
# SUPABASE_SERVICE_ROLE_KEY`
},
cloudflare: {
filename: 'wrangler.toml',
content: `name = "miri-dev-project"
main = "src/index.js"
compatibility_date = "2024-01-01"
[env.production]
name = "miri-dev-project"
route = "miri.dev/*"
[env.staging]
name = "miri-dev-project-staging"
route = "staging.miri.dev/*"
[env.production.vars]
ENVIRONMENT = "production"
[env.staging.vars]
ENVIRONMENT = "staging"
[pages]
build_output_dir = "out"
build_command = "npm run build && npm run export"
# 환경 변수는 다음 명령어로 설정하세요:
# wrangler secret put NEXT_PUBLIC_SUPABASE_URL
# wrangler secret put NEXT_PUBLIC_SUPABASE_ANON_KEY
# wrangler secret put SUPABASE_SERVICE_ROLE_KEY`
},
github: {
filename: '.github/workflows/deploy.yml',
content: 'name: Deploy to GitHub Pages\n\n' +
'on:\n' +
' push:\n' +
' branches: [ main ]\n' +
' pull_request:\n' +
' branches: [ main ]\n' +
' workflow_dispatch:\n\n' +
'permissions:\n' +
' contents: read\n' +
' pages: write\n' +
' id-token: write\n\n' +
'concurrency:\n' +
' group: "pages"\n' +
' cancel-in-progress: false\n\n' +
'jobs:\n' +
' build:\n' +
' runs-on: ubuntu-latest\n' +
' \n' +
' steps:\n' +
' - name: Checkout\n' +
' uses: actions/checkout@v4\n' +
' \n' +
' - name: Setup Node.js\n' +
' uses: actions/setup-node@v4\n' +
' with:\n' +
' node-version: \'18\'\n' +
' cache: \'npm\'\n' +
' \n' +
' - name: Install dependencies\n' +
' run: npm ci\n' +
' \n' +
' - name: Build application\n' +
' run: npm run build\n' +
' env:\n' +
` NEXT_PUBLIC_SUPABASE_URL: ${GITHUB_SECRETS.SUPABASE_URL}\n` +
` NEXT_PUBLIC_SUPABASE_ANON_KEY: ${GITHUB_SECRETS.SUPABASE_ANON_KEY}\n` +
` SUPABASE_SERVICE_ROLE_KEY: ${GITHUB_SECRETS.SUPABASE_SERVICE_ROLE_KEY}\n` +
' \n' +
' - name: Setup Pages\n' +
' uses: actions/configure-pages@v4\n' +
' with:\n' +
' static_site_generator: next\n' +
' \n' +
' - name: Upload artifact\n' +
' uses: actions/upload-pages-artifact@v3\n' +
' with:\n' +
' path: ./out\n\n' +
' deploy:\n' +
' environment:\n' +
' name: github-pages\n' +
` url: ${GITHUB_SECRETS.PAGE_URL}\n` +
' runs-on: ubuntu-latest\n' +
' needs: build\n' +
' if: github.ref == \'refs/heads/main\'\n' +
' \n' +
' steps:\n' +
' - name: Deploy to GitHub Pages\n' +
' id: deployment\n' +
' uses: actions/deploy-pages@v4'
},
aws: {
filename: 'aws/buildspec.yml',
content: 'version: 0.2\n\n' +
'env:\n' +
' variables:\n' +
' NODE_ENV: production\n' +
' parameter-store:\n' +
' NEXT_PUBLIC_SUPABASE_URL: /miri-dev/supabase/url\n' +
' NEXT_PUBLIC_SUPABASE_ANON_KEY: /miri-dev/supabase/anon-key\n' +
' SUPABASE_SERVICE_ROLE_KEY: /miri-dev/supabase/service-role-key\n\n' +
'phases:\n' +
' install:\n' +
' runtime-versions:\n' +
' nodejs: 18\n' +
' commands:\n' +
' - echo Installing dependencies...\n' +
' - npm ci --only=production\n' +
' \n' +
' pre_build:\n' +
' commands:\n' +
' - echo Pre-build phase started on `date`\n' +
' - echo Setting up environment variables...\n' +
' \n' +
' build:\n' +
' commands:\n' +
' - echo Build phase started on `date`\n' +
' - npm run build\n' +
' - echo Build completed on `date`\n' +
' \n' +
' post_build:\n' +
' commands:\n' +
' - echo Post-build phase started on `date`\n' +
' - echo Preparing distribution files...\n' +
' - aws s3 sync out/ s3://$BUCKET_NAME --delete --cache-control max-age=31536000\n' +
' - aws s3 cp out/index.html s3://$BUCKET_NAME/index.html --cache-control max-age=0\n' +
' - aws cloudfront create-invalidation --distribution-id $CLOUDFRONT_DISTRIBUTION_ID --paths "/*"\n' +
' \n' +
'artifacts:\n' +
' files:\n' +
' - \'**/*\'\n' +
' base-directory: out\n' +
' name: miri-dev-build-$(date +%Y-%m-%d-%H-%M-%S)\n\n' +
'cache:\n' +
' paths:\n' +
' - node_modules/**/*\n' +
' - .next/cache/**/*'
}
};
function createDirectoryIfNotExists(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
function exportConfig(platform) {
const template = templates[platform];
if (!template) {
console.error(chalk.red(`❌ 지원하지 않는 플랫폼: ${platform}`));
console.log(chalk.yellow('지원 플랫폼: vercel, netlify, cloudflare, github, aws'));
return;
}
const filePath = template.filename;
const dirPath = path.dirname(filePath);
if (dirPath !== '.' && dirPath !== '') {
createDirectoryIfNotExists(dirPath);
}
let content;
if (typeof template.content === 'string') {
content = template.content;
} else {
content = JSON.stringify(template.content, null, 2);
}
try {
fs.writeFileSync(filePath, content);
console.log(chalk.green(`✅ ${platform} 설정 파일 생성 완료: ${filePath}`));
// 플랫폼별 추가 안내
const instructions = {
vercel: [
'다음 단계:',
'1. vercel login',
'2. vercel',
'3. vercel env add (환경 변수 설정)',
'4. vercel --prod'
],
netlify: [
'다음 단계:',
'1. netlify login',
'2. netlify init',
'3. netlify env:set (환경 변수 설정)',
'4. netlify deploy --prod'
],
cloudflare: [
'다음 단계:',
'1. wrangler login',
'2. wrangler pages create',
'3. wrangler secret put (환경 변수 설정)',
'4. wrangler pages deploy out'
],
github: [
'다음 단계:',
'1. GitHub 저장소의 Settings > Secrets에서 환경 변수 설정',
'2. Settings > Pages에서 GitHub Actions 소스 선택',
'3. 코드를 main 브랜치에 push'
],
aws: [
'다음 단계:',
'1. AWS CLI 설정: aws configure',
'2. CloudFormation 스택 생성 (aws/cloudformation.yml 참조)',
'3. Parameter Store에 환경 변수 설정',
'4. CodeBuild 프로젝트 실행'
]
};
if (instructions[platform]) {
console.log(chalk.blue('\n📋 ' + instructions[platform].join('\n ')));
}
} catch (error) {
console.error(chalk.red(`❌ 파일 생성 실패: ${error.message}`));
}
}
function exportAll() {
console.log(chalk.blue('🚀 모든 플랫폼 설정 파일 생성 중...\n'));
Object.keys(templates).forEach(platform => {
exportConfig(platform);
console.log('');
});
console.log(chalk.green('✨ 모든 설정 파일 생성 완료!'));
console.log(chalk.yellow('\n💡 Tip: 각 플랫폼의 환경 변수 설정을 잊지 마세요:'));
console.log(chalk.white(' - NEXT_PUBLIC_SUPABASE_URL'));
console.log(chalk.white(' - NEXT_PUBLIC_SUPABASE_ANON_KEY'));
console.log(chalk.white(' - SUPABASE_SERVICE_ROLE_KEY'));
}
function showHelp() {
console.log(chalk.blue('🚀 Miri.dev Export 도구'));
console.log('');
console.log(chalk.white('사용법:'));
console.log(' miri export <platform> # 특정 플랫폼 설정 생성');
console.log(' miri export all # 모든 플랫폼 설정 생성');
console.log('');
console.log(chalk.white('지원 플랫폼:'));
console.log(' vercel # Vercel 배포 설정');
console.log(' netlify # Netlify 배포 설정');
console.log(' cloudflare # Cloudflare Pages 설정');
console.log(' github # GitHub Pages 자동 배포');
console.log(' aws # AWS S3 + CloudFront 설정');
console.log('');
console.log(chalk.white('예시:'));
console.log(' miri export vercel # vercel.json 생성');
console.log(' miri export all # 모든 설정 파일 생성');
}
module.exports = function (platform) {
if (!platform || platform === 'help') {
showHelp();
return;
}
if (platform === 'all') {
exportAll();
return;
}
exportConfig(platform);
};
// CLI에서 직접 실행될 때
if (require.main === module) {
const platform = process.argv[2];
module.exports(platform);
}