chore: 패키지 버전을 0.4.2로 업데이트 및 릴리즈 노트 추가 #59
Workflow file for this run
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: Build and Release | |
| on: | |
| push: | |
| tags: | |
| - "v*.*.*" | |
| workflow_dispatch: | |
| inputs: | |
| tag_name: | |
| description: "Tag name for release (e.g., v0.1.1)" | |
| required: true | |
| default: "v0.1.1" | |
| env: | |
| # Mask sensitive values in logs | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| # CI and build configuration | |
| CI: true | |
| # Code signing configuration | |
| CSC_IDENTITY_AUTO_DISCOVERY: true | |
| permissions: | |
| contents: write | |
| jobs: | |
| # === 2단계 빌드/배포 아키텍처 === | |
| # Job 1: Build - 앱 빌드 및 코드사이닝만 수행 | |
| build: | |
| runs-on: macos-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: "20" | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v2 | |
| with: | |
| version: latest | |
| - name: Get pnpm store directory | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV | |
| - name: Cache pnpm dependencies | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ env.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Fix electron installation | |
| run: | | |
| if [ -f "node_modules/electron/install.js" ]; then | |
| node node_modules/electron/install.js | |
| fi | |
| shell: bash | |
| - name: Build application | |
| run: pnpm run build | |
| - name: Import Apple certificate | |
| run: | | |
| if [ -n "$APPLE_CERTIFICATE" ]; then | |
| echo "Setting up Apple certificate..." | |
| # Create variables for keychain | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| KEYCHAIN_PASSWORD=$(openssl rand -base64 32) | |
| # Decode certificate from base64 (secure method) | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 | |
| # Create temporary keychain | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| # Import certificate (GitHub Actions masks the password in logs) | |
| security import certificate.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH login.keychain-db | |
| security default-keychain -s $KEYCHAIN_PATH | |
| # Allow codesign to access the certificate without password prompt | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH >/dev/null 2>&1 | |
| echo "Certificate imported successfully" | |
| # Verify certificate (minimal output) | |
| if security find-identity -p codesigning $KEYCHAIN_PATH >/dev/null 2>&1; then | |
| echo "Certificate verification successful" | |
| else | |
| echo "Certificate verification failed" | |
| exit 1 | |
| fi | |
| # Clean up certificate file | |
| rm -f certificate.p12 | |
| else | |
| echo "No Apple certificate found in secrets" | |
| fi | |
| - name: Validate secrets | |
| run: | | |
| # Apple 인증서 관련 secrets 검증 | |
| if [ -z "$APPLE_CERTIFICATE" ]; then | |
| echo "❌ ERROR: APPLE_CERTIFICATE secret이 설정되지 않았습니다." | |
| echo "macOS 코드 사이닝을 위해 APPLE_CERTIFICATE secret을 설정해주세요." | |
| exit 1 | |
| fi | |
| if [ -z "$APPLE_CERTIFICATE_PASSWORD" ]; then | |
| echo "❌ ERROR: APPLE_CERTIFICATE_PASSWORD secret이 설정되지 않았습니다." | |
| echo "macOS 코드 사이닝을 위해 APPLE_CERTIFICATE_PASSWORD secret을 설정해주세요." | |
| exit 1 | |
| fi | |
| # Apple ID 관련 secrets 검증 | |
| if [ -z "$APPLE_ID" ]; then | |
| echo "❌ ERROR: APPLE_ID secret이 설정되지 않았습니다." | |
| echo "macOS 노타리제이션을 위해 APPLE_ID secret을 설정해주세요." | |
| exit 1 | |
| fi | |
| if [ -z "$APPLE_APP_SPECIFIC_PASSWORD" ]; then | |
| echo "❌ ERROR: APPLE_APP_SPECIFIC_PASSWORD secret이 설정되지 않았습니다." | |
| echo "macOS 노타리제이션을 위해 APPLE_APP_SPECIFIC_PASSWORD secret을 설정해주세요." | |
| exit 1 | |
| fi | |
| if [ -z "$APPLE_TEAM_ID" ]; then | |
| echo "❌ ERROR: APPLE_TEAM_ID secret이 설정되지 않았습니다." | |
| echo "macOS 노타리제이션을 위해 APPLE_TEAM_ID secret을 설정해주세요." | |
| exit 1 | |
| fi | |
| echo "✅ macOS 코드 사이닝 및 노타리제이션 secrets가 모두 설정되었습니다." | |
| shell: bash | |
| - name: Package application | |
| run: | | |
| # Check if certificate was imported (suppress sensitive output) | |
| if security find-identity -p codesigning | grep -q "Developer ID Application" 2>/dev/null; then | |
| echo "Certificate found, building with code signing and notarization" | |
| # Debug: Check environment variables before building (completely safe - no values exposed) | |
| echo "Environment variables for notarization:" | |
| if [ -n "$APPLE_ID" ]; then echo " APPLE_ID: ***SET***"; else echo " APPLE_ID: NOT_SET"; fi | |
| if [ -n "$APPLE_APP_SPECIFIC_PASSWORD" ]; then echo " APPLE_APP_SPECIFIC_PASSWORD: ***SET***"; else echo " APPLE_APP_SPECIFIC_PASSWORD: NOT_SET"; fi | |
| if [ -n "$APPLE_TEAM_ID" ]; then echo " APPLE_TEAM_ID: ***SET***"; else echo " APPLE_TEAM_ID: NOT_SET"; fi | |
| echo " CI: $CI" | |
| # === 2단계 빌드/배포 아키텍처 - 1단계: 빌드만 === | |
| # 빌드만 수행하고 GitHub 배포는 하지 않음 (--publish never) | |
| # 2단계에서 별도 액션(softprops/action-gh-release)이 배포 담당 | |
| pnpm run package:gh-action | |
| # Verify signing (minimal output to avoid exposing sensitive info) | |
| echo "Verifying code signature..." | |
| for app in release/**/*.app; do | |
| if [ -e "$app" ]; then | |
| echo "Checking: $(basename "$app")" | |
| if codesign --verify --deep --strict "$app" >/dev/null 2>&1; then | |
| echo "✓ Code signature valid for $(basename "$app")" | |
| else | |
| echo "⚠ Code signature verification failed for $(basename "$app")" | |
| fi | |
| fi | |
| done | |
| else | |
| echo "Unexpected: Certificate not found after validation" | |
| exit 1 | |
| fi | |
| - name: List built files | |
| run: | | |
| echo "=== Built files for macOS ===" | |
| find . -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.app" \) -exec ls -la {} \; | |
| - name: Upload artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-artifacts | |
| path: | | |
| release/*.dmg | |
| release/*.zip | |
| release/*.yml | |
| release/*.blockmap | |
| retention-days: 5 | |
| # Job 2: Release - 빌드된 파일들을 GitHub Releases에 배포 | |
| release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_files: ${{ steps.get_files.outputs.files }} | |
| tag_name: ${{ steps.get_tag.outputs.tag_name }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Get tag name | |
| id: get_tag | |
| run: echo "tag_name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT | |
| - name: Download all artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| merge-multiple: true | |
| - name: List downloaded artifacts | |
| run: | | |
| echo "=== Downloaded artifacts ===" | |
| find . -type f \( -name "*.dmg" -o -name "*.zip" -o -name "*.yml" -o -name "*.blockmap" \) -exec ls -la {} \; | |
| echo "=== All files in current directory ===" | |
| ls -la | |
| - name: Get release files info | |
| id: get_files | |
| run: | | |
| echo "files<<EOF" >> $GITHUB_OUTPUT | |
| find . -type f \( -name "*.dmg" -o -name "*.zip" \) | while read file; do | |
| filename=$(basename "$file") | |
| size=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file" 2>/dev/null || echo "0") | |
| echo "{\"name\":\"$filename\",\"size\":$size}" | |
| done | jq -s . >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| # 릴리즈 노트 파일 확인 | |
| - name: Check for release notes | |
| id: check_release_notes | |
| run: | | |
| TAG_NAME=${GITHUB_REF#refs/tags/} | |
| RELEASE_NOTES_FILE="release-notes/${TAG_NAME}.md" | |
| if [ -f "$RELEASE_NOTES_FILE" ]; then | |
| echo "release_notes_exists=true" >> $GITHUB_OUTPUT | |
| echo "release_notes_file=$RELEASE_NOTES_FILE" >> $GITHUB_OUTPUT | |
| echo "Found release notes file: $RELEASE_NOTES_FILE" | |
| else | |
| echo "release_notes_exists=false" >> $GITHUB_OUTPUT | |
| echo "No release notes file found for $TAG_NAME, will use auto-generated notes" | |
| fi | |
| # === 2단계 빌드/배포 아키텍처 - 2단계: 배포만 === | |
| # 1단계에서 생성된 파일들을 GitHub Releases에 배포 | |
| # electron-builder 대신 GitHub Actions 전용 액션 사용 | |
| # 장점: 더 안정적, 유연한 배포 옵션, 자동 릴리즈 노트 생성 | |
| - name: Create Release (with custom release notes) | |
| if: steps.check_release_notes.outputs.release_notes_exists == 'true' | |
| id: create_release_custom | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| draft: false | |
| prerelease: false | |
| body_path: ${{ steps.check_release_notes.outputs.release_notes_file }} | |
| files: | | |
| *.dmg | |
| *.zip | |
| *.yml | |
| *.blockmap | |
| - name: Create Release (with auto-generated notes) | |
| if: steps.check_release_notes.outputs.release_notes_exists == 'false' | |
| id: create_release_auto | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| files: | | |
| *.dmg | |
| *.zip | |
| *.yml | |
| *.blockmap | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Job 3: Deploy Pages - 다운로드 랜딩 페이지 생성 및 배포 | |
| deploy-pages: | |
| if: always() | |
| needs: release | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pages: write | |
| id-token: write | |
| environment: | |
| name: github-pages | |
| url: ${{ steps.deployment.outputs.page_url }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| - name: Setup Pages | |
| uses: actions/configure-pages@v4 | |
| - name: Generate landing page using script | |
| run: | | |
| # Use tag from GitHub ref if release job outputs are not available | |
| if [ -n "${{ needs.release.outputs.tag_name }}" ]; then | |
| TAG_NAME="${{ needs.release.outputs.tag_name }}" | |
| else | |
| TAG_NAME="${GITHUB_REF#refs/tags/}" | |
| fi | |
| # Run the page generation script | |
| ./scripts/generate-page.sh "$TAG_NAME" "${{ github.repository }}" | |
| - name: Upload Pages artifact | |
| uses: actions/upload-pages-artifact@v3 | |
| with: | |
| path: "_site" | |
| - name: Deploy to GitHub Pages | |
| id: deployment | |
| uses: actions/deploy-pages@v4 |