UNPKG

miridev-cli

Version:

Official CLI tool for deploying static sites to miri.dev - Deploy your websites in seconds

236 lines (192 loc) 7.21 kB
const chalk = require('chalk'); const ora = require('ora'); const path = require('path'); const { exec } = require('child_process'); const { promisify } = require('util'); const { scanFiles, loadConfig, loadIgnoreFile, formatFileSize } = require('../utils/files'); const api = require('../utils/api'); const { getCurrentUser } = require('../utils/auth'); const execAsync = promisify(exec); /** * 배포 명령어 실행 */ async function deploy(options) { console.log(chalk.blue.bold('\n🚀 Starting deployment to miri.dev\n')); try { const deployDir = path.resolve(options.dir); console.log(chalk.gray(`📂 Deploy directory: ${deployDir}`)); // 기존 사이트 배포인지 확인 if (options.site) { const siteId = extractSiteId(options.site); console.log(chalk.cyan(`🔄 Updating existing site: ${options.site}`)); console.log(chalk.gray(`🆔 Site ID: ${siteId}`)); } // 1. 설정 파일 로드 const config = await loadConfig(options.config); console.log(chalk.gray(`⚙️ Config file: ${options.config}`)); // 2. 빌드 프로세스 (설정된 경우) if (!options.skipBuild && config.build && config.build.command) { await runBuildProcess(config.build); } // 실제 배포 디렉토리 결정 const actualDeployDir = config.build && config.build.directory ? path.resolve(deployDir, config.build.directory) : deployDir; // 3. 파일 스캔 const ignorePatterns = [ ...(config.deploy?.ignore || []), ...(await loadIgnoreFile(actualDeployDir)) ]; const scanOptions = { ignorePatterns, includeHidden: config.deploy?.includeHidden || false, maxFileSize: parseFileSize(config.deploy?.maxFileSize || '25MB') }; const { files, totalSize, totalFiles } = await scanFiles(actualDeployDir, scanOptions); // 4. 파일 목록 표시 console.log(chalk.cyan('\n📋 Files to deploy:')); files.slice(0, 10).forEach(file => { console.log(` ${chalk.gray('→')} ${file.relativePath} ${chalk.gray(`(${formatFileSize(file.size)})`)}`); }); if (files.length > 10) { console.log(` ${chalk.gray(`... and ${files.length - 10} more files`)}`); } // 5. 사용자 확인 const user = getCurrentUser(); if (user) { console.log(chalk.gray(`\n👤 Deploying as: ${user.email} (${user.plan || 'basic'})`)); } else { console.log(chalk.gray('\n👤 Deploying as guest (1 hour hosting)')); } // 6. 배포 실행 const deployType = options.site ? 'Updating site...' : 'Uploading files...'; const spinner = ora(deployType).start(); try { const result = await api.deployFiles(files, { siteName: options.name || config.site?.name, customDomain: config.site?.customDomain, existingSite: options.site ? extractSiteId(options.site) : null, debug: options.debug }); spinner.succeed(options.site ? 'Site updated!' : 'Upload completed!'); // 7. 결과 표시 if (options.site) { console.log(chalk.green.bold('\n✨ Site update successful!\n')); console.log(`🔄 Updated site: ${chalk.cyan.underline(result.site.url)}`); } else { console.log(chalk.green.bold('\n✨ Deployment successful!\n')); console.log(`🌐 Site URL: ${chalk.cyan.underline(result.site.url)}`); } if (result.site.customDomain) { console.log(`🔗 Custom domain: ${chalk.cyan.underline(`https://${result.site.customDomain}`)}`); } console.log(`📊 Files deployed: ${totalFiles}`); console.log(`📦 Total size: ${formatFileSize(totalSize)}`); console.log(`🆔 Site ID: ${result.site.id}`); if (result.site.expiresAt) { const expiryDate = new Date(result.site.expiresAt); console.log(`⏰ Expires: ${expiryDate.toLocaleString()}`); } // 8. 브라우저에서 열기 (옵션) if (options.open) { await openInBrowser(result.site.url); } console.log(chalk.gray('\n💡 Tip: Use "miridev status" to check deployment status')); } catch (error) { spinner.fail(options.site ? 'Site update failed' : 'Upload failed'); throw error; } } catch (error) { console.error(chalk.red.bold('\n✗ Deployment failed:')); console.error(chalk.red(error.message)); if (error.message.includes('index.html')) { console.log(chalk.yellow('\n💡 Make sure you have an index.html file in your project root')); } if (error.message.includes('파일 크기')) { console.log(chalk.yellow('\n💡 Try reducing file sizes or exclude large files using .miriignore')); } if (error.message.includes('Site not found') || error.message.includes('Invalid site')) { console.log(chalk.yellow('\n💡 Check the site ID/URL. Use "miridev sites" to list your sites')); } process.exit(1); } } /** * 빌드 프로세스 실행 */ async function runBuildProcess(buildConfig) { console.log(chalk.gray(`🔨 Running build command: ${buildConfig.command}`)); const spinner = ora('Building project...').start(); try { const { stdout, stderr } = await execAsync(buildConfig.command, { cwd: process.cwd(), encoding: 'utf8' }); spinner.succeed('Build completed'); if (stdout) { console.log(chalk.gray('Build output:')); console.log(chalk.gray(stdout)); } if (stderr) { console.log(chalk.yellow('Build warnings:')); console.log(chalk.yellow(stderr)); } } catch (error) { spinner.fail('Build failed'); console.error(chalk.red('Build error:')); console.error(chalk.red(error.stdout || error.message)); throw new Error('Build process failed'); } } /** * 파일 크기 문자열을 바이트로 변환 */ function parseFileSize(sizeStr) { const units = { B: 1, KB: 1024, MB: 1024 * 1024, GB: 1024 * 1024 * 1024 }; const match = sizeStr.match(/^(\d+(\.\d+)?)\s*(B|KB|MB|GB)$/i); if (!match) { throw new Error(`Invalid file size format: ${sizeStr}`); } const size = parseFloat(match[1]); const unit = match[3].toUpperCase(); return size * units[unit]; } /** * 브라우저에서 URL 열기 */ async function openInBrowser(url) { const open = require('open'); try { await open(url); console.log(chalk.gray(`🌐 Opened ${url} in browser`)); } catch (error) { console.log(chalk.yellow(`⚠️ Could not open browser: ${error.message}`)); console.log(chalk.gray(`Please visit: ${url}`)); } } /** * 사이트 ID 추출 (도메인 또는 ID에서) */ function extractSiteId(siteInput) { // e7a5d1de.miri.dev -> e7a5d1de if (siteInput.includes('.miri.dev')) { return siteInput.split('.')[0]; } // 이미 사이트 ID인 경우 (8자리 hex) if (/^[a-f0-9]{8}$/.test(siteInput)) { return siteInput; } // 전체 URL인 경우 (https://e7a5d1de.miri.dev) const urlMatch = siteInput.match(/\/\/([a-f0-9]{8})\.miri\.dev/); if (urlMatch) { return urlMatch[1]; } // 기본적으로 입력값 그대로 반환 (에러는 서버에서 처리) return siteInput; } module.exports = deploy;