Merge pull request #118 from hamlsy/feat/117 #114
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Deploy to S3 | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| env: | |
| AWS_REGION: ap-northeast-2 | |
| NODE_VERSION: '18' | |
| jobs: | |
| # 빌드 및 테스트 | |
| build: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| token: ${{ secrets.GIT_PERSONAL_ACCESS_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 18 | |
| # cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Setup environment | |
| run: | | |
| # 환경 결정 (개발/프로덕션만) | |
| #if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| ENV="production" | |
| #else | |
| # ENV="development" | |
| #fi | |
| # 환경변수 설정 | |
| node scripts/setup-env.js $ENV setup | |
| echo "ENVIRONMENT=$ENV" >> $GITHUB_ENV | |
| - name: Validate environment | |
| run: npm run env:validate | |
| - name: Lint code | |
| run: npm run lint | |
| - name: Clean build artifacts and cache | |
| run: | | |
| echo "🧹 Cleaning dist folder..." | |
| rm -rf dist | |
| echo "🧹 Cleaning node_modules cache..." | |
| rm -rf node_modules/.cache | |
| echo "🧹 Cleaning npm cache..." | |
| npm cache clean --force | |
| echo "✅ Cleanup completed!" | |
| - name: Build application | |
| run: | | |
| #if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| # npm run build:prod | |
| npm run build:prod | |
| #else | |
| # npm run build:dev | |
| #fi | |
| # 빌드된 index.html 확인 | |
| echo "📋 Checking built index.html..." | |
| echo "JS files referenced:" | |
| grep -o 'src="[^"]*\.js"' dist/index.html || echo "No JS references found" | |
| # 실제 생성된 JS 파일 확인 | |
| echo "📋 Actual JS files in dist:" | |
| ls -lh dist/js/*.js || echo "No JS files found" | |
| # 참조와 실제 파일이 일치하는지 검증 | |
| JS_REF=$(grep -o 'src="/js/app\.[^"]*\.js"' dist/index.html | sed 's|src="/js/||;s|"||' | head -1) | |
| if [ -n "$JS_REF" ]; then | |
| echo "Checking if referenced file exists: $JS_REF" | |
| if [ -f "dist/js/$JS_REF" ]; then | |
| echo "✅ Referenced file exists: $JS_REF" | |
| else | |
| echo "❌ ERROR: Referenced file NOT found: $JS_REF" | |
| echo "Available files:" | |
| ls -lh dist/js/ | |
| exit 1 | |
| fi | |
| fi | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| retention-days: 1 | |
| # S3 배포 | |
| deploy: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| if: github.event_name == 'push' | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: recursive | |
| token: ${{ secrets.GIT_PERSONAL_ACCESS_TOKEN }} | |
| - name: Download build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: dist | |
| path: dist/ | |
| - name: Configure AWS credentials | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | |
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | |
| aws-region: ap-northeast-2 | |
| - name: Deploy to S3 | |
| run: | | |
| # 환경 결정 | |
| #if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then | |
| ENV="production" | |
| # DEPLOY_PATH="/" | |
| #else | |
| # ENV="development" | |
| # DEPLOY_PATH="/dev/" | |
| #fi | |
| DEPLOY_PATH="" | |
| BUCKET="${{ secrets.S3_BUCKET }}" | |
| echo "Deploying to $ENV environment..." | |
| echo "S3 Bucket: $BUCKET" | |
| echo "Deploy Path: $DEPLOY_PATH" | |
| # S3에 파일 업로드 (경로별 배포) | |
| # 정적 파일들 업로드 (캐시 설정, HTML/JSON 제외) | |
| aws s3 sync dist/ s3://$BUCKET$DEPLOY_PATH \ | |
| --cache-control "public, max-age=31536000, immutable" \ | |
| --exclude "*.html" --exclude "*.json" | |
| # HTML 파일은 캐시하지 않음 | |
| aws s3 cp dist/index.html s3://$BUCKET$DEPLOY_PATH/index.html \ | |
| --cache-control "no-cache, no-store, must-revalidate" | |
| # manifest.json 파일이 있으면 업로드 | |
| if [ -f "dist/manifest.json" ]; then | |
| aws s3 cp dist/manifest.json s3://$BUCKET$DEPLOY_PATH/manifest.json \ | |
| --cache-control "no-cache, no-store, must-revalidate" | |
| fi | |
| # Welcome 페이지 업로드 | |
| if [ -f "dist/welcome/index.html" ]; then | |
| aws s3 cp dist/welcome/index.html s3://$BUCKET$DEPLOY_PATH/welcome/index.html \ | |
| --cache-control "no-cache, no-store, must-revalidate" | |
| echo "✅ Welcome page uploaded" | |
| else | |
| echo "⚠️ Welcome page not found in dist/welcome/index.html" | |
| fi | |
| echo "Deployment completed successfully!" | |
| echo "Application URL: https://$BUCKET.s3-website.ap-northeast-2.amazonaws.com$DEPLOY_PATH" | |
| - name: CloudFront Invalidation | |
| env: | |
| CLOUD_FRONT_ID: ${{ secrets.AWS_CLOUDFRONT_ID}} | |
| run: | | |
| if [ -n "$CLOUD_FRONT_ID" ]; then | |
| echo "Invalidating CloudFront cache..." | |
| # 무효화 요청 생성 | |
| INVALIDATION_OUTPUT=$(aws cloudfront create-invalidation \ | |
| --distribution-id $CLOUD_FRONT_ID \ | |
| --paths "/*") | |
| if [ $? -eq 0 ]; then | |
| # 무효화 ID 추출 | |
| INVALIDATION_ID=$(echo "$INVALIDATION_OUTPUT" | grep -oP '"Id"\s*:\s*"\K[^"]+' | head -1) | |
| if [ -n "$INVALIDATION_ID" ]; then | |
| echo "✅ CloudFront cache invalidation initiated: $INVALIDATION_ID" | |
| # 무효화 완료까지 대기 (최대 15분) | |
| echo "Waiting for invalidation to complete (this may take up to 15 minutes)..." | |
| MAX_WAIT_TIME=900 # 15분 (초) | |
| WAIT_INTERVAL=10 # 10초마다 확인 | |
| ELAPSED_TIME=0 | |
| while [ $ELAPSED_TIME -lt $MAX_WAIT_TIME ]; do | |
| sleep $WAIT_INTERVAL | |
| ELAPSED_TIME=$((ELAPSED_TIME + WAIT_INTERVAL)) | |
| STATUS_OUTPUT=$(aws cloudfront get-invalidation \ | |
| --distribution-id $CLOUD_FRONT_ID \ | |
| --id $INVALIDATION_ID 2>&1) | |
| if [ $? -eq 0 ]; then | |
| STATUS=$(echo "$STATUS_OUTPUT" | grep -oP '"Status"\s*:\s*"\K[^"]+' | head -1) | |
| if [ "$STATUS" == "Completed" ]; then | |
| echo "✅ CloudFront cache invalidation completed!" | |
| break | |
| elif [ "$STATUS" == "InProgress" ]; then | |
| PROGRESS=$((ELAPSED_TIME * 100 / MAX_WAIT_TIME)) | |
| echo "Invalidation in progress... (${PROGRESS}% of max wait time elapsed)" | |
| fi | |
| fi | |
| done | |
| if [ $ELAPSED_TIME -ge $MAX_WAIT_TIME ]; then | |
| echo "⚠️ Invalidation is still in progress after maximum wait time. It will continue in the background." | |
| fi | |
| else | |
| echo "⚠️ Failed to extract invalidation ID from response" | |
| fi | |
| else | |
| echo "⚠️ Failed to invalidate CloudFront cache" | |
| fi | |
| else | |
| echo "⚠️ CloudFront ID not configured, skipping invalidation" | |
| fi | |
| - name: Notify deployment | |
| if: always() | |
| run: | | |
| if [[ "${{ job.status }}" == "success" ]]; then | |
| echo "✅ Deployment successful!" | |
| else | |
| echo "❌ Deployment failed!" | |
| fi |