fix(app): align day chip, checkbox, and scrollbar with Figma (#137) #27
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 Moa App | |
| on: | |
| push: | |
| branches: [main] | |
| tags: ['v*'] | |
| workflow_dispatch: | |
| inputs: | |
| version: | |
| description: 'Release version (e.g., v1.0.0)' | |
| required: true | |
| type: string | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| jobs: | |
| # 빌드 대상 버전과 실행 여부를 결정한다. | |
| # - 태그 push / workflow_dispatch: 항상 빌드 (명시적 의도) | |
| # - main push: 릴리스 PR 머지로 tauri.conf.json 이 변경되고, 해당 버전 릴리스가 | |
| # 아직 없을 때만 빌드 (일반 push 는 skip) | |
| resolve: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| version: ${{ steps.v.outputs.version }} | |
| tag: ${{ steps.v.outputs.tag }} | |
| is_prerelease: ${{ steps.v.outputs.is_prerelease }} | |
| should_build: ${{ steps.v.outputs.should_build }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Resolve version | |
| id: v | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REF_TYPE: ${{ github.ref_type }} | |
| REF_NAME: ${{ github.ref_name }} | |
| INPUT_VERSION: ${{ inputs.version }} | |
| BEFORE: ${{ github.event.before }} | |
| SHA: ${{ github.sha }} | |
| run: | | |
| if [ "$EVENT_NAME" = "push" ] && [ "$REF_TYPE" = "tag" ]; then | |
| VERSION="${REF_NAME#v}" | |
| SHOULD_BUILD=true | |
| elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| VERSION="${INPUT_VERSION#v}" | |
| SHOULD_BUILD=true | |
| else | |
| # main push: 버전은 package.json 에서 읽고, 릴리스 커밋인지 검증한다. | |
| VERSION="$(jq -r .version apps/app/package.json)" | |
| SHOULD_BUILD=false | |
| CHANGED=$(gh api "repos/$REPO/compare/${BEFORE}...${SHA}" --jq '.files[].filename' 2>/dev/null || true) | |
| if echo "$CHANGED" | grep -qx 'apps/app/src-tauri/tauri.conf.json'; then | |
| if ! gh release view "v$VERSION" --repo "$REPO" >/dev/null 2>&1; then | |
| SHOULD_BUILD=true | |
| else | |
| echo "릴리스 v$VERSION 가 이미 존재하여 빌드를 건너뜁니다." | |
| fi | |
| else | |
| echo "tauri.conf.json 변경이 없어 릴리스 push 가 아닙니다. 빌드를 건너뜁니다." | |
| fi | |
| fi | |
| TAG="v${VERSION}" | |
| if [[ "$VERSION" == *-* ]]; then IS_PRE=true; else IS_PRE=false; fi | |
| { | |
| echo "version=$VERSION" | |
| echo "tag=$TAG" | |
| echo "is_prerelease=$IS_PRE" | |
| echo "should_build=$SHOULD_BUILD" | |
| } >> "$GITHUB_OUTPUT" | |
| echo "resolved: version=$VERSION tag=$TAG prerelease=$IS_PRE should_build=$SHOULD_BUILD" | |
| notify-start: | |
| needs: [resolve] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Discord Build Start Notification | |
| continue-on-error: true | |
| env: | |
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| VERSION: ${{ needs.resolve.outputs.tag }} | |
| ACTOR: ${{ github.actor }} | |
| run: | | |
| TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| PAYLOAD=$(jq -n \ | |
| --arg version "$VERSION" \ | |
| --arg actor "$ACTOR" \ | |
| --arg timestamp "$TIMESTAMP" \ | |
| '{ | |
| embeds: [{ | |
| title: ("🚀 Moa App " + $version + " 빌드 시작"), | |
| color: 3447003, | |
| fields: [ | |
| {name: "버전", value: $version, inline: true}, | |
| {name: "배포자", value: $actor, inline: true}, | |
| {name: "대상", value: "macOS (aarch64), macOS (x86_64), Windows"} | |
| ], | |
| timestamp: $timestamp | |
| }] | |
| }') | |
| curl -f -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" \ | |
| "$DISCORD_WEBHOOK" | |
| # draft 릴리스를 매트릭스 이전에 단 한 번 생성한다. | |
| # 매트릭스 잡들이 각자 릴리스를 생성/조회하면 동시 접근 경쟁으로 | |
| # GitHub 이 간헐적 401(Requires authentication) 을 반환하므로, | |
| # 생성을 분리하고 매트릭스는 release_id 로 업로드만 한다. | |
| create-release: | |
| needs: [resolve, notify-start] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| release_id: ${{ steps.create.outputs.result }} | |
| steps: | |
| - name: Create draft release | |
| id: create | |
| uses: actions/github-script@v7 | |
| env: | |
| TAG: ${{ needs.resolve.outputs.tag }} | |
| IS_PRERELEASE: ${{ needs.resolve.outputs.is_prerelease }} | |
| with: | |
| result-encoding: string | |
| script: | | |
| const body = [ | |
| `## Moa ${process.env.TAG}`, | |
| '', | |
| '### 설치 방법', | |
| '', | |
| '#### macOS', | |
| '- **Apple Silicon**: `moa_<version>_aarch64.dmg`', | |
| '- **Intel** (x86_64): `moa_<version>_x64.dmg`', | |
| '', | |
| '`.dmg` 파일을 다운로드하여 Applications 폴더로 드래그하세요.', | |
| '', | |
| '#### Windows', | |
| '- **EXE**: `moa_<version>_x64-setup.exe`', | |
| '', | |
| '#### 자동 업데이트', | |
| '기존 사용자는 자동 업데이트 알림을 받습니다.', | |
| ].join('\n') | |
| const { data } = await github.rest.repos.createRelease({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| tag_name: process.env.TAG, | |
| name: `Moa ${process.env.TAG}`, | |
| body, | |
| draft: true, | |
| prerelease: process.env.IS_PRERELEASE === 'true', | |
| }) | |
| return data.id | |
| publish-tauri: | |
| needs: [resolve, create-release] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| permissions: | |
| contents: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: 'macos-latest' | |
| args: '--target aarch64-apple-darwin' | |
| - platform: 'macos-latest' | |
| args: '--target x86_64-apple-darwin' | |
| - platform: 'windows-latest' | |
| args: '--bundles nsis,updater' | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@v2 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} | |
| - name: Rust cache | |
| uses: swatinem/rust-cache@v2 | |
| with: | |
| workspaces: './src-tauri -> target' | |
| - name: Install frontend dependencies | |
| run: bun install | |
| - name: Import Apple Certificate (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| run: | | |
| CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" \ | |
| -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| - name: Setup Apple API Key (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| env: | |
| APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_CONTENT }} | |
| run: | | |
| KEY_PATH="$HOME/private_keys/AuthKey_${{ secrets.APPLE_API_KEY }}.p8" | |
| mkdir -p "$(dirname "$KEY_PATH")" | |
| echo -n "$APPLE_API_KEY_CONTENT" | base64 --decode -o "$KEY_PATH" | |
| echo "APPLE_API_KEY_PATH=$KEY_PATH" >> $GITHUB_ENV | |
| - name: Build and release | |
| uses: tauri-apps/tauri-action@action-v0.6.1 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| KAKAO_REST_API_KEY: ${{ secrets.KAKAO_REST_API_KEY }} | |
| KAKAO_CLIENT_SECRET: ${{ secrets.KAKAO_CLIENT_SECRET }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| APPLE_CLIENT_ID: ${{ secrets.APPLE_CLIENT_ID }} | |
| APPLE_KEY_ID: ${{ secrets.APPLE_KEY_ID }} | |
| APPLE_PRIVATE_KEY: ${{ secrets.APPLE_PRIVATE_KEY }} | |
| with: | |
| # 릴리스는 create-release 잡에서 이미 생성됨 → ID 로 업로드만 수행. | |
| releaseId: ${{ needs.create-release.outputs.release_id }} | |
| includeUpdaterJson: true | |
| args: ${{ matrix.args }} | |
| # GitHub 자동 생성 릴리스 노트("What's Changed")를 draft 릴리스 본문에 덧붙인다. | |
| # 베타/정식 모두 적용 — 베타는 draft 로 남지만 노트는 미리 채워둔다. | |
| release-notes: | |
| needs: [resolve, publish-tauri] | |
| if: needs.resolve.outputs.should_build == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Generate and append release notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.resolve.outputs.tag }} | |
| run: | | |
| CUR=$(gh release view "$TAG" --repo "$REPO" --json body --jq .body) | |
| if echo "$CUR" | grep -q "What's Changed"; then | |
| echo "릴리스 노트가 이미 존재하여 건너뜁니다." | |
| exit 0 | |
| fi | |
| NOTES=$(gh api "repos/$REPO/releases/generate-notes" \ | |
| -f tag_name="$TAG" --jq .body 2>/dev/null || true) | |
| if [ -z "$NOTES" ]; then | |
| echo "생성된 노트가 없어 본문을 그대로 둡니다." | |
| exit 0 | |
| fi | |
| gh release edit "$TAG" --repo "$REPO" --notes "$CUR | |
| $NOTES" | |
| # 정식 릴리스만 draft 를 해제(publish)한다 → 전체 사용자 자동 업데이트 트리거. | |
| # 베타(-beta 등 prerelease)는 draft 로 남겨 자동 업데이트를 막는다. | |
| publish-release: | |
| needs: [resolve, release-notes] | |
| if: needs.resolve.outputs.is_prerelease == 'false' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Publish release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| TAG: ${{ needs.resolve.outputs.tag }} | |
| run: gh release edit "$TAG" --repo "$REPO" --draft=false | |
| # publish 는 GITHUB_TOKEN 으로 수행되어 release:published 이벤트가 deploy 를 | |
| # 트리거하지 못하므로, 같은 런에서 reusable workflow 로 직접 호출한다. | |
| deploy: | |
| needs: [resolve, publish-release] | |
| if: needs.resolve.outputs.is_prerelease == 'false' | |
| uses: ./.github/workflows/deploy-app.yml | |
| with: | |
| version: ${{ needs.resolve.outputs.tag }} | |
| secrets: | |
| DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| notify-end: | |
| needs: [resolve, publish-tauri] | |
| runs-on: ubuntu-latest | |
| if: always() && needs.resolve.outputs.should_build == 'true' | |
| steps: | |
| - name: Discord Notification | |
| continue-on-error: true | |
| env: | |
| DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| BUILD_RESULT: ${{ needs.publish-tauri.result }} | |
| VERSION: ${{ needs.resolve.outputs.tag }} | |
| ACTOR: ${{ github.actor }} | |
| REPO: ${{ github.repository }} | |
| run: | | |
| if [ "$BUILD_RESULT" == "success" ]; then | |
| COLOR=5763719 | |
| TITLE="✅ Moa App $VERSION 빌드 성공" | |
| else | |
| COLOR=15548997 | |
| TITLE="❌ Moa App $VERSION 빌드 실패" | |
| fi | |
| RELEASE_URL="https://github.com/${REPO}/releases" | |
| TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ) | |
| PAYLOAD=$(jq -n \ | |
| --arg title "$TITLE" \ | |
| --argjson color "$COLOR" \ | |
| --arg version "$VERSION" \ | |
| --arg actor "$ACTOR" \ | |
| --arg release_url "$RELEASE_URL" \ | |
| --arg timestamp "$TIMESTAMP" \ | |
| '{ | |
| embeds: [{ | |
| title: $title, | |
| color: $color, | |
| fields: [ | |
| {name: "버전", value: $version, inline: true}, | |
| {name: "배포자", value: $actor, inline: true}, | |
| {name: "릴리스", value: $release_url} | |
| ], | |
| timestamp: $timestamp | |
| }] | |
| }') | |
| curl -f -H "Content-Type: application/json" \ | |
| -d "$PAYLOAD" \ | |
| "$DISCORD_WEBHOOK" |