Android 앱 개발 자동화 파이프라인 완벽 가이드

Android 앱 개발 자동화 파이프라인 완벽 가이드

Android 앱 개발 자동화 파이프라인 완벽 가이드

OpenCode + CI/CD를 활용한 시장수요조사 → 배포 자동화

당신의 고급 기술 스택(Docker, Linux, DevOps 경험)을 고려하여, 완전 자동화 파이프라인을 단계별로 구성하겠습니다.


📋 전체 아키텍처 개요

┌─────────────────────────────────────────────────────────────────┐
│                     자동화 파이프라인 전체 흐름                    │
└─────────────────────────────────────────────────────────────────┘
1️⃣ 시장수요조사 자동화 (OpenCode Agent)
   └─ 웹 크롤링 + 시장 분석 리포트 자동 생성
2️⃣ 앱 개발 자동화 (OpenCode Agent)
   └─ 요구사항 → Android Studio 프로젝트 자동 생성
   └─ Kotlin/Java 코드 자동 작성
3️⃣ 빌드 및 테스트 자동화 (GitHub Actions)
   ├─ Gradle 빌드 (APK/AAB 생성)
   ├─ 유닛 테스트 자동 실행
   ├─ Lint 체크
   └─ APK 서명
4️⃣ 검수 및 메타데이터 생성 (자동화 스크립트)
   ├─ 앱 스크린샷 자동 캡처
   ├─ Google Play 메타데이터 생성
   ├─ 프라이버시 정책, 이용약관 자동 작성
   └─ QA 리포트 생성
5️⃣ Google Play Store 업로드 (Fastlane + GitHub Actions)
   ├─ 내부 테스트 트랙 배포
   ├─ 베타 테스트 트랙 배포
   └─ 프로덕션 배포 (수동 승인 후)

🔧 Step 1: OpenCode 설치 및 환경 구성

1.1 OpenCode 설치

# Node.js를 이용한 설치 (권장)
npm install -g opencode
# 또는 Homebrew (macOS/Linux)
brew install opencode/tap/opencode
# Docker를 사용한 설치
docker run -it -v ~/.opencode:/home/user/.opencode opencode/cli:latest

1.2 OpenCode 설정

# OpenCode 초기화
opencode init
# LLM 프로바이더 설정 (OpenCode Zen 추천)
opencode /connect
# 또는 Claude, GPT-4 등 직접 설정
export ANTHROPIC_API_KEY="your-key"
export OPENAI_API_KEY="your-key"

1.3 프로젝트 구조 준비

mkdir -p android-automation-workspace
cd android-automation-workspace
# 다음 디렉토리 구조 생성
mkdir -p {market_research,app_dev,ci_cd,metadata,scripts}
# AGENTS.md 생성 (OpenCode가 프로젝트 이해할 수 있도록)
cat > AGENTS.md << 'EOF'
# Android App Automation Project
## Project Structure
- market_research/: 시장 조사 자동화
- app_dev/: 앱 개발 자동화
- ci_cd/: CI/CD 파이프라인
- metadata/: Play Store 메타데이터
- scripts/: 보조 자동화 스크립트
## Tech Stack
- Android: Kotlin/Java, Gradle
- CI/CD: GitHub Actions
- Deployment: Fastlane, Google Play API
- Automation: Python, Bash
## Key Goals
1. Market research automation
2. App development automation
3. Automated testing and QA
4. Metadata generation
5. Google Play Store deployment
EOF

📊 Step 2: 시장수요조사 자동화

2.1 OpenCode를 활용한 시장 분석

프롬프트 작성:

cat > market_research/research_prompt.txt << 'EOF'
당신은 모바일 앱 시장 분석가입니다.
다음 작업을 자동으로 수행하세요:
1. Google Play Store 트렌드 분석
   - 현재 인기 카테고리 (상위 5개)
   - 각 카테고리별 평균 다운로드 수
   - 사용자 평점 분포
2. 경쟁 분석
   - 키워드 [productivity, health, utility] 관련 앱 상위 10개
   - 각 앱의 평점, 리뷰 수, 가격
   - 주요 기능과 차별점
3. 사용자 요구사항 분석
   - 최근 3개월 사용자 리뷰의 공통 불만사항
   - 자주 요청되는 기능
   - 개선 기회 영역
4. 출력 형식
   JSON 형식으로 다음 구조로 저장:
   {
     "market_overview": {...},
     "top_competitors": [...],
     "user_pain_points": [...],
     "opportunities": [...],
     "recommended_features": [...]
   }
EOF
# OpenCode 실행
opencode analyze --file market_research/research_prompt.txt --output market_research/analysis_result.json

2.2 Python 스크립트로 시장조사 자동화

# market_research/auto_research.py
import json
import requests
from datetime import datetime
from anthropic import Anthropic
client = Anthropic()
def market_research_automation(app_category: str, keywords: list) -> dict:
    """
    OpenCode/Claude를 활용한 자동화된 시장조사
    """
    
    conversation_history = []
    
    # 1단계: 초기 시장 분석 요청
    user_message = f"""
    {app_category} 카테고리에서 {', '.join(keywords)} 키워드로 검색한 
    Google Play Store 앱들의 시장 분석을 수행해주세요.
    
    다음 정보를 수집해야 합니다:
    1. 상위 10개 앱의 기본정보 (이름, 다운로드 수, 평점, 리뷰 수)
    2. 각 앱의 주요 기능 분석
    3. 사용자 리뷰에서 발견되는 공통 불만사항
    4. 시장 기회 및 차별화 전략
    
    결과는 JSON 형식으로 제공해주세요.
    """
    
    conversation_history.append({
        "role": "user",
        "content": user_message
    })
    
    # Claude API 호출
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=4000,
        messages=conversation_history
    )
    
    assistant_message = response.content[0].text
    conversation_history.append({
        "role": "assistant",
        "content": assistant_message
    })
    
    # 2단계: 상세 분석 요청
    followup = """
    위의 분석을 바탕으로, 우리가 개발할 앱이 경쟁에서 이길 수 있는
    3-5가지 차별화된 기능을 제안해주세요. 
    각 기능마다:
    - 기능명
    - 기술 난이도 (낮음/중간/높음)
    - 예상 개발 시간
    - 사용자 영향도
    를 포함해주세요.
    """
    
    conversation_history.append({
        "role": "user",
        "content": followup
    })
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2000,
        messages=conversation_history
    )
    
    final_analysis = response.content[0].text
    
    return {
        "timestamp": datetime.now().isoformat(),
        "category": app_category,
        "keywords": keywords,
        "initial_analysis": assistant_message,
        "feature_recommendations": final_analysis,
        "report_file": f"market_research/report_{datetime.now().strftime('%Y%m%d_%H%M%S')}.md"
    }
if __name__ == "__main__":
    result = market_research_automation(
        app_category="Productivity",
        keywords=["task manager", "todo list", "productivity"]
    )
    
    # 결과 저장
    with open(result["report_file"], "w") as f:
        f.write(f"# Market Research Report\n")
        f.write(f"Generated: {result['timestamp']}\n\n")
        f.write(f"## Initial Analysis\n{result['initial_analysis']}\n\n")
        f.write(f"## Feature Recommendations\n{result['feature_recommendations']}\n")
    
    print(f"✅ Report saved: {result['report_file']}")
    print(json.dumps(result, indent=2))

실행:

cd market_research
python auto_research.py

🚀 Step 3: 앱 개발 자동화

3.1 OpenCode를 사용한 Android 프로젝트 생성

# app_dev 디렉토리로 이동
cd app_dev
# OpenCode에게 Android 프로젝트 생성 지시
cat > create_app_prompt.txt << 'EOF'
Android Studio 프로젝트를 자동으로 생성해주세요.
프로젝트 요구사항:
- 프로젝트명: MyProductivityApp
- Package: com.example.productivity
- Minimum SDK: 24 (Android 7.0)
- Target SDK: 35 (Android 15, 2025 Play Store 요구사항)
- 언어: Kotlin
- 아키텍처: MVVM + Clean Architecture
필요한 모듈:
1. app (메인 앱)
2. data (로컬 DB, 네트워크 통신)
3. domain (비즈니스 로직)
4. presentation (UI/ViewModel)
기본 화면 구조:
- MainActivity (메인 탭 네비게이션)
  - TaskListFragment (할일 목록)
  - AddTaskFragment (할일 추가)
  - SettingsFragment (설정)
기본 기능:
- Room DB를 사용한 로컬 저장소
- LiveData를 사용한 상태 관리
- Material Design 3 UI
build.gradle.kts 구성:
- Gradle 8.5+
- Kotlin 1.9+
- Jetpack libraries (Room, ViewModel, LiveData)
- Hilt 의존성 주입
생성 완료 후, 이 프로젝트를 GitHub에 푸시할 수 있는 상태로 만들어주세요.
EOF
# OpenCode로 프로젝트 생성
opencode --file create_app_prompt.txt --output app_structure.md

3.2 자동화된 프로젝트 초기화 스크립트

#!/bin/bash
# app_dev/init_android_project.sh
set -e
PROJECT_NAME="MyProductivityApp"
PACKAGE_NAME="com.example.productivity"
MIN_SDK=24
TARGET_SDK=35
KOTLIN_VERSION="1.9.23"
GRADLE_VERSION="8.5"
echo "🔨 Android 프로젝트 초기화 중..."
# 1. 프로젝트 디렉토리 생성
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
# 2. settings.gradle.kts 생성
cat > settings.gradle.kts << 'SETTINGS_EOF'
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}
rootProject.name = "MyProductivityApp"
include(":app", ":data", ":domain", ":presentation")
SETTINGS_EOF
# 3. 루트 build.gradle.kts 생성
cat > build.gradle.kts << 'BUILD_EOF'
plugins {
    id("com.android.application") version "8.2.0" apply false
    id("com.android.library") version "8.2.0" apply false
    id("org.jetbrains.kotlin.android") version "1.9.23" apply false
    id("com.google.dagger.hilt.android") version "2.48" apply false
}
BUILD_EOF
# 4. 모듈별 디렉토리 및 build.gradle.kts 생성
create_module() {
    local module_name=$1
    local is_app=$2
    
    mkdir -p "$module_name/src/main/java/com/example/productivity"
    mkdir -p "$module_name/src/main/res/{layout,drawable,values,mipmap}"
    mkdir -p "$module_name/src/test/java"
    mkdir -p "$module_name/src/androidTest/java"
    
    if [ "$is_app" = "true" ]; then
        cat > "$module_name/build.gradle.kts" << 'APP_BUILD_EOF'
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.dagger.hilt.android")
    kotlin("kapt")
}
android {
    namespace = "com.example.productivity"
    compileSdk = 35
    defaultConfig {
        applicationId = "com.example.productivity"
        minSdk = 24
        targetSdk = 35
        versionCode = 1
        versionName = "1.0.0"
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
    buildFeatures {
        viewBinding = true
        dataBinding = true
    }
}
dependencies {
    // Jetpack
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
    implementation("androidx.activity:activity-ktx:1.8.1")
    implementation("androidx.fragment:fragment-ktx:1.6.2")
    // Room
    implementation("androidx.room:room-runtime:2.6.1")
    kapt("androidx.room:room-compiler:2.6.1")
    implementation("androidx.room:room-ktx:2.6.1")
    // Hilt
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-compiler:2.48")
    // Kotlin Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    // Testing
    testImplementation("junit:junit:4.13.2")
    testImplementation("org.mockito:mockito-core:5.7.0")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
APP_BUILD_EOF
    else
        cat > "$module_name/build.gradle.kts" << 'LIB_BUILD_EOF'
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
    id("com.google.dagger.hilt.android")
    kotlin("kapt")
}
android {
    namespace = "com.example.productivity.$module_name"
    compileSdk = 35
    defaultConfig {
        minSdk = 24
        targetSdk = 35
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions {
        jvmTarget = "17"
    }
}
dependencies {
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("com.google.dagger:hilt-android:2.48")
    kapt("com.google.dagger:hilt-compiler:2.48")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
    testImplementation("junit:junit:4.13.2")
}
LIB_BUILD_EOF
    fi
}
create_module "app" "true"
create_module "data" "false"
create_module "domain" "false"
create_module "presentation" "false"
# 5. gradlew 파일 생성
gradle wrapper --gradle-version 8.5
echo "✅ Android 프로젝트 초기화 완료!"
echo "📁 프로젝트 경로: $(pwd)"

실행:

chmod +x app_dev/init_android_project.sh
app_dev/init_android_project.sh

🔄 Step 4: CI/CD 파이프라인 구성 (GitHub Actions)

4.1 GitHub Actions 워크플로우 구성

# .github/workflows/android-ci-cd.yml
name: Android CI/CD Pipeline
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]
  workflow_dispatch:
    inputs:
      deploy_track:
        description: 'Deploy track (internal, beta, production)'
        required: false
        default: 'internal'
env:
  JAVA_VERSION: "17"
  GRADLE_VERSION: "8.5"
  MIN_SDK: 24
  TARGET_SDK: 35
jobs:
  build:
    name: Build & Test
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'
          cache: gradle
      
      - name: Make gradlew executable
        run: chmod +x ./gradlew
      
      - name: Run lint checks
        run: ./gradlew lint
      
      - name: Run unit tests
        run: ./gradlew testDebugUnitTest
      
      - name: Run instrumented tests
        uses: reactivecircus/android-emulator-runner@v2
        with:
          api-level: 31
          script: ./gradlew connectedAndroidTest
      
      - name: Build Release APK
        run: ./gradlew assembleRelease
      
      - name: Build Release AAB
        run: ./gradlew bundleRelease
      
      - name: Sign APK
        uses: r0adkll/sign-android-release@v1
        id: sign_apk
        with:
          releaseDirectory: app/build/outputs/apk/release
          signingKeyBase64: ${{ secrets.SIGNING_KEY }}
          alias: ${{ secrets.KEY_ALIAS }}
          keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
          keyPassword: ${{ secrets.KEY_PASSWORD }}
      
      - name: Sign AAB
        uses: r0adkll/sign-android-release@v1
        id: sign_aab
        with:
          releaseDirectory: app/build/outputs/bundle/release
          signingKeyBase64: ${{ secrets.SIGNING_KEY }}
          alias: ${{ secrets.KEY_ALIAS }}
          keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
          keyPassword: ${{ secrets.KEY_PASSWORD }}
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: release-artifacts
          path: |
            app/build/outputs/apk/release/*.apk
            app/build/outputs/bundle/release/*.aab
      
      - name: Generate test report
        if: always()
        run: |
          mkdir -p build/reports
          cp -r app/build/reports/tests build/reports/ || true
          cp app/build/reports/lint-results.html build/reports/ || true
  deploy:
    name: Deploy to Google Play
    needs: build
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: release-artifacts
      
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
          bundler-cache: true
      
      - name: Install Fastlane
        run: |
          sudo gem install fastlane
          cd android && fastlane install_plugins
      
      - name: Deploy to internal testing
        env:
          PLAY_STORE_SERVICE_ACCOUNT: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT }}
        run: |
          cd android
          fastlane deploy_internal
      
      - name: Create GitHub Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: v${{ github.run_number }}
          release_name: Release ${{ github.run_number }}
          draft: false
          prerelease: false
  manual_deployment:
    name: Manual Deploy to Google Play
    runs-on: ubuntu-latest
    if: github.event_name == 'workflow_dispatch'
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: ${{ env.JAVA_VERSION }}
          distribution: 'temurin'
          cache: gradle
      
      - name: Build Release AAB
        run: |
          chmod +x ./gradlew
          ./gradlew bundleRelease
      
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: '3.3'
          bundler-cache: true
      
      - name: Install Fastlane
        run: sudo gem install fastlane
      
      - name: Deploy to specified track
        env:
          PLAY_STORE_SERVICE_ACCOUNT: ${{ secrets.PLAY_STORE_SERVICE_ACCOUNT }}
          DEPLOY_TRACK: ${{ github.event.inputs.deploy_track }}
        run: |
          echo "$PLAY_STORE_SERVICE_ACCOUNT" > service_account.json
          fastlane android deploy_to_play_store \
            track:$DEPLOY_TRACK \
            aab:app/build/outputs/bundle/release/app-release.aab

4.2 Fastlane 구성

# android/fastlane/Fastfile
default_platform(:android)
platform :android do
  
  desc "Deploy to internal testing track"
  lane :deploy_internal do
    upload_to_play_store(
      track: 'internal',
      aab: '../app/build/outputs/bundle/release/app-release.aab',
      skip_upload_metadata: false,
      skip_upload_images: false,
      skip_upload_screenshots: false
    )
  end
  desc "Deploy to beta testing track"
  lane :deploy_beta do
    upload_to_play_store(
      track: 'beta',
      aab: '../app/build/outputs/bundle/release/app-release.aab'
    )
  end
  desc "Deploy to production"
  lane :deploy_production do
    upload_to_play_store(
      track: 'production',
      aab: '../app/build/outputs/bundle/release/app-release.aab',
      rollout: '0.1'  # 10%부터 롤아웃 시작
    )
  end
  desc "Upload metadata only"
  lane :upload_metadata do
    upload_to_play_store(
      skip_upload_aab: true,
      skip_upload_apk: true
    )
  end
end

📝 Step 5: 메타데이터 자동 생성

5.1 Google Play Store 메타데이터 자동화

# metadata/auto_metadata_generator.py
import os
import json
from datetime import datetime
from anthropic import Anthropic
client = Anthropic()
def generate_play_store_metadata(app_name: str, app_description: str, features: list) -> dict:
    """
    Google Play Store 메타데이터 자동 생성
    """
    
    features_text = "\n".join([f"- {f}" for f in features])
    
    prompt = f"""
    당신은 Google Play Store 최적화 전문가입니다.
    
    다음 앱의 메타데이터를 작성해주세요:
    
    앱 이름: {app_name}
    앱 설명: {app_description}
    주요 기능:
    {features_text}
    
    다음 항목들을 JSON 형식으로 생성해주세요:
    
    1. short_description (80자 이내)
    2. full_description (4000자 이내, Google Play 정책 준수)
    3. app_title_variations (3-5개 변형)
    4. keywords (10-15개, SEO 최적화)
    5. privacy_policy_summary
    6. category (Google Play 카테고리 중 하나)
    7. content_rating (Google Play 콘텐츠 등급)
    
    응답은 유효한 JSON만 포함하세요.
    """
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2000,
        messages=[{
            "role": "user",
            "content": prompt
        }]
    )
    
    metadata_json = json.loads(response.content[0].text)
    
    return metadata_json
def generate_privacy_policy(app_name: str, collects_data: dict) -> str:
    """
    개인정보 보호정책 자동 생성
    """
    
    data_types = "\n".join([f"- {k}: {v}" for k, v in collects_data.items()])
    
    prompt = f"""
    {app_name} 앱을 위한 개인정보 보호정책을 작성해주세요.
    
    수집되는 데이터:
    {data_types}
    
    Google Play 스토어 요구사항을 충족하고, 
    일반적인 프라이버시 정책 구조를 따르세요:
    
    1. 개요
    2. 수집되는 정보
    3. 정보 사용
    4. 정보 공유
    5. 보안
    6. 데이터 보관
    7. 사용자 권리
    8. 정책 변경
    9. 연락처
    
    마크다운 형식으로 작성해주세요.
    """
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=3000,
        messages=[{
            "role": "user",
            "content": prompt
        }]
    )
    
    return response.content[0].text
def generate_terms_of_service(app_name: str) -> str:
    """
    이용약관 자동 생성
    """
    
    prompt = f"""
    {app_name} 모바일 앱 서비스를 위한 
    표준 이용약관(Terms of Service)을 마크다운 형식으로 작성해주세요.
    
    포함해야 할 항목:
    1. 서비스 이용 규칙
    2. 사용자 책임
    3. 지적 재산권
    4. 면책조항
    5. 서비스 중단
    6. 변경사항
    7. 준거법
    8. 분쟁 해결
    
    Google Play 스토어 정책을 준수하는 형식으로 작성해주세요.
    """
    
    response = client.messages.create(
        model="claude-3-5-sonnet-20241022",
        max_tokens=2500,
        messages=[{
            "role": "user",
            "content": prompt
        }]
    )
    
    return response.content[0].text
if __name__ == "__main__":
    # 메타데이터 생성
    metadata = generate_play_store_metadata(
        app_name="Productivity Master",
        app_description="A comprehensive task management app",
        features=[
            "Real-time task synchronization",
            "Offline support",
            "Custom reminders",
            "Team collaboration",
            "Beautiful UI with Material Design 3"
        ]
    )
    
    # 파일로 저장
    output_dir = "metadata/generated"
    os.makedirs(output_dir, exist_ok=True)
    
    with open(f"{output_dir}/metadata.json", "w") as f:
        json.dump(metadata, f, indent=2, ensure_ascii=False)
    
    # 개인정보 보호정책
    privacy_policy = generate_privacy_policy(
        app_name="Productivity Master",
        collects_data={
            "Task data": "로컬 저장소에만 저장",
            "User preferences": "로컬 저장소에만 저장",
            "Analytics": "크래시 리포팅용"
        }
    )
    
    with open(f"{output_dir}/PRIVACY_POLICY.md", "w") as f:
        f.write(privacy_policy)
    
    # 이용약관
    terms = generate_terms_of_service("Productivity Master")
    
    with open(f"{output_dir}/TERMS_OF_SERVICE.md", "w") as f:
        f.write(terms)
    
    print("✅ 메타데이터 생성 완료!")
    print(f"📁 저장 위치: {output_dir}")
    print(json.dumps(metadata, indent=2, ensure_ascii=False))

실행:

cd metadata
python auto_metadata_generator.py

🔐 Step 6: Google Play Store 업로드 준비

6.1 서비스 계정 설정

#!/bin/bash
# scripts/setup_play_store_auth.sh
echo "🔐 Google Play Store 인증 설정 시작..."
# 1. Google Cloud 프로젝트에서 서비스 계정 생성
echo "1️⃣ Google Cloud Console에 접속하세요: https://console.cloud.google.com"
echo "   - 'IAM 및 관리' > '서비스 계정' 선택"
echo "   - '서비스 계정 만들기' 클릭"
echo "   - 이름: play-store-deployer"
echo "   - 역할: 편집자"
# 2. JSON 키 생성
echo ""
echo "2️⃣ 서비스 계정 상세 페이지에서:"
echo "   - '키' 탭 > '새 키 추가' > 'JSON' 선택"
echo "   - 다운로드된 JSON 파일을 저장하세요"
# 3. Google Play Console에 등록
echo ""
echo "3️⃣ Google Play Console에서:"
echo "   - 계정 설정 > 개발자 계정 > 사용자 및 권한"
echo "   - 서비스 계정 권한 추가"
echo "   - 서비스 계정의 이메일 주소 추가"
echo "   - 권한: 모든 권한 (또는 필요한 권한만 선택)"
# 4. GitHub Secrets 설정
echo ""
echo "4️⃣ GitHub Repository에서:"
echo "   - Settings > Secrets and variables > Actions"
echo "   - 'New repository secret' 추가"
echo "   - 이름: PLAY_STORE_SERVICE_ACCOUNT"
echo "   - 값: 다운로드한 JSON 파일 전체 내용"
# 5. 서명 키 생성
echo ""
echo "5️⃣ 앱 서명 키 생성:"
mkdir -p keystore
# 테스트용 키스토어 생성 (프로덕션에서는 안전한 키 사용)
keytool -genkey -v \
  -keystore keystore/release.keystore \
  -keyalias release-key \
  -keyalg RSA \
  -keysize 2048 \
  -validity 10000
# 키스토어를 Base64로 인코딩
echo ""
echo "6️⃣ GitHub Secrets에 추가할 항목:"
KEY_BASE64=$(base64 -i keystore/release.keystore)
echo "SIGNING_KEY: $KEY_BASE64"
echo ""
echo "✅ 설정 완료!"

실행:

chmod +x scripts/setup_play_store_auth.sh
scripts/setup_play_store_auth.sh

📊 Step 7: 완전 자동화 통합 스크립트

7.1 엔드-투-엔드 자동화

# scripts/full_automation.py
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from datetime import datetime
from pathlib import Path
class AndroidAutomationPipeline:
    def __init__(self, project_root: str):
        self.root = Path(project_root)
        self.timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        self.log_file = self.root / f"automation_log_{self.timestamp}.txt"
        
    def log(self, message: str, level: str = "INFO"):
        """로그 기록"""
        log_msg = f"[{datetime.now().isoformat()}] [{level}] {message}"
        print(log_msg)
        with open(self.log_file, "a") as f:
            f.write(log_msg + "\n")
    
    def run_command(self, cmd: str, description: str):
        """명령어 실행 및 로깅"""
        self.log(f"Running: {description}")
        try:
            result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
            if result.returncode == 0:
                self.log(f"✅ {description} - SUCCESS")
                return True
            else:
                self.log(f"❌ {description} - FAILED", "ERROR")
                self.log(f"Error: {result.stderr}", "ERROR")
                return False
        except Exception as e:
            self.log(f"❌ Exception in {description}: {str(e)}", "ERROR")
            return False
    
    def stage_1_market_research(self):
        """1단계: 시장수요조사"""
        self.log("=" * 50)
        self.log("STAGE 1: 시장수요조사")
        self.log("=" * 50)
        
        research_script = self.root / "market_research" / "auto_research.py"
        success = self.run_command(
            f"python {research_script}",
            "Market Research"
        )
        return success
    
    def stage_2_app_development(self):
        """2단계: 앱 개발"""
        self.log("=" * 50)
        self.log("STAGE 2: 앱 개발 자동화")
        self.log("=" * 50)
        
        init_script = self.root / "app_dev" / "init_android_project.sh"
        success = self.run_command(
            f"bash {init_script}",
            "Android Project Initialization"
        )
        
        if success:
            self.log("Generating Kotlin source files...")
            # OpenCode를 사용한 소스 코드 생성
            success = self.run_command(
                f"opencode --file {self.root}/app_dev/create_app_prompt.txt",
                "Generate Source Code"
            )
        
        return success
    
    def stage_3_build_test(self):
        """3단계: 빌드 및 테스트"""
        self.log("=" * 50)
        self.log("STAGE 3: 빌드 및 테스트")
        self.log("=" * 50)
        
        app_dir = self.root / "MyProductivityApp"
        os.chdir(app_dir)
        
        # 린트 체크
        success = self.run_command(
            "./gradlew lint",
            "Lint Check"
        )
        
        if not success:
            return False
        
        # 유닛 테스트
        success = self.run_command(
            "./gradlew testDebugUnitTest",
            "Unit Tests"
        )
        
        if not success:
            return False
        
        # Release 빌드
        success = self.run_command(
            "./gradlew bundleRelease -x test",
            "Build Release Bundle (AAB)"
        )
        
        if not success:
            return False
        
        # APK 빌드 (테스트용)
        success = self.run_command(
            "./gradlew assembleRelease -x test",
            "Build Release APK"
        )
        
        return success
    
    def stage_4_metadata_generation(self):
        """4단계: 메타데이터 생성"""
        self.log("=" * 50)
        self.log("STAGE 4: 메타데이터 자동 생성")
        self.log("=" * 50)
        
        metadata_script = self.root / "metadata" / "auto_metadata_generator.py"
        success = self.run_command(
            f"python {metadata_script}",
            "Generate Google Play Metadata"
        )
        
        return success
    
    def stage_5_upload_to_play_store(self, track: str = "internal"):
        """5단계: Google Play Store 업로드"""
        self.log("=" * 50)
        self.log(f"STAGE 5: Google Play Store 업로드 ({track})")
        self.log("=" * 50)
        
        app_dir = self.root / "MyProductivityApp"
        os.chdir(app_dir)
        
        # Fastlane으로 배포
        if track == "internal":
            success = self.run_command(
                "fastlane android deploy_internal",
                "Deploy to Internal Testing"
            )
        elif track == "beta":
            success = self.run_command(
                "fastlane android deploy_beta",
                "Deploy to Beta Testing"
            )
        elif track == "production":
            success = self.run_command(
                "fastlane android deploy_production",
                "Deploy to Production"
            )
        else:
            self.log(f"Unknown track: {track}", "ERROR")
            return False
        
        return success
    
    def generate_report(self):
        """최종 리포트 생성"""
        self.log("=" * 50)
        self.log("최종 리포트 생성 중...")
        self.log("=" * 50)
        
        report_file = self.root / f"automation_report_{self.timestamp}.json"
        
        report = {
            "timestamp": datetime.now().isoformat(),
            "automation_duration": "see log file",
            "stages_completed": {
                "market_research": "✅",
                "app_development": "✅",
                "build_and_test": "✅",
                "metadata_generation": "✅",
                "play_store_upload": "✅"
            },
            "artifacts": {
                "market_research_report": str(self.root / "market_research" / "report_*.md"),
                "android_project": str(self.root / "MyProductivityApp"),
                "aab_file": str(self.root / "MyProductivityApp" / "app" / "build" / "outputs" / "bundle" / "release" / "app-release.aab"),
                "apk_file": str(self.root / "MyProductivityApp" / "app" / "build" / "outputs" / "apk" / "release" / "app-release.apk"),
                "metadata": str(self.root / "metadata" / "generated"),
                "log_file": str(self.log_file)
            },
            "next_steps": [
                "✅ 시장 조사 완료 - 시장 기회와 차별화 전략 확인",
                "✅ 앱 개발 완료 - 자동 생성된 프로젝트 검토",
                "✅ 테스트 통과 - 빌드 및 단위 테스트 완료",
                "✅ 메타데이터 생성 - Google Play 스토어 정보 자동 작성",
                "✅ 스토어 업로드 - 내부 테스트 트랙에 배포"
            ]
        }
        
        with open(report_file, "w") as f:
            json.dump(report, f, indent=2, ensure_ascii=False)
        
        self.log(f"✅ 최종 리포트: {report_file}")
        return report
    
    def run_full_pipeline(self, track: str = "internal"):
        """완전 자동화 파이프라인 실행"""
        self.log(f"🚀 Android 앱 자동화 파이프라인 시작")
        self.log(f"프로젝트 경로: {self.root}")
        
        try:
            # 1단계
            if not self.stage_1_market_research():
                raise Exception("Market research failed")
            
            # 2단계
            if not self.stage_2_app_development():
                raise Exception("App development failed")
            
            # 3단계
            if not self.stage_3_build_test():
                raise Exception("Build and test failed")
            
            # 4단계
            if not self.stage_4_metadata_generation():
                raise Exception("Metadata generation failed")
            
            # 5단계
            if not self.stage_5_upload_to_play_store(track):
                raise Exception("Play Store upload failed")
            
            # 리포트 생성
            report = self.generate_report()
            
            self.log("=" * 50)
            self.log("🎉 전체 파이프라인 성공적으로 완료!")
            self.log("=" * 50)
            
            return True
        
        except Exception as e:
            self.log(f"❌ 파이프라인 실패: {str(e)}", "ERROR")
            return False
if __name__ == "__main__":
    project_root = os.getcwd()
    track = sys.argv[1] if len(sys.argv) > 1 else "internal"
    
    pipeline = AndroidAutomationPipeline(project_root)
    success = pipeline.run_full_pipeline(track)
    
    sys.exit(0 if success else 1)

실행:

cd android-automation-workspace
# 내부 테스트 트랙으로 배포
python scripts/full_automation.py internal
# 또는 베타 테스트 트랙
python scripts/full_automation.py beta
# 또는 프로덕션
python scripts/full_automation.py production

🔄 Step 8: 자동화 스케줄링

8.1 정기적 자동화 실행

#!/bin/bash
# scripts/schedule_automation.sh
# Cron 작업 설정 (예: 매주 월요일 00:00)
cat > /tmp/automation_cron << 'EOF'
0 0 * * 1 cd /path/to/android-automation-workspace && python scripts/full_automation.py internal 2>&1 | tee -a automation_scheduler.log
EOF
crontab /tmp/automation_cron
# 또는 systemd 타이머 사용
mkdir -p ~/.config/systemd/user/
cat > ~/.config/systemd/user/android-automation.service << 'EOF'
[Unit]
Description=Android App Automation Pipeline
After=network-online.target
[Service]
Type=oneshot
WorkingDirectory=/path/to/android-automation-workspace
ExecStart=/usr/bin/python3 /path/to/android-automation-workspace/scripts/full_automation.py internal
EOF
cat > ~/.config/systemd/user/android-automation.timer << 'EOF'
[Unit]
Description=Run Android App Automation Weekly
[Timer]
OnCalendar=Mon *-*-* 00:00:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl --user daemon-reload
systemctl --user enable android-automation.timer
systemctl --user start android-automation.timer

🎯 Step 9: 모니터링 및 알림

9.1 실시간 모니터링

# scripts/monitor_automation.py
import os
import json
import smtplib
from datetime import datetime
from pathlib import Path
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
class AutomationMonitor:
    def __init__(self, workspace_path: str, email_config: dict):
        self.workspace = Path(workspace_path)
        self.email_config = email_config
    
    def check_latest_log(self) -> dict:
        """최신 로그 확인"""
        log_files = sorted(self.workspace.glob("automation_log_*.txt"), reverse=True)
        
        if not log_files:
            return {"status": "ERROR", "message": "No log files found"}
        
        latest_log = log_files[0]
        with open(latest_log) as f:
            content = f.read()
        
        status = "SUCCESS" if "성공적으로 완료" in content else "FAILED"
        
        return {
            "status": status,
            "log_file": str(latest_log),
            "timestamp": latest_log.stem.split("_", 2)[2],
            "content_preview": content[-500:]
        }
    
    def send_email_notification(self, status: dict):
        """이메일 알림 전송"""
        try:
            sender_email = self.email_config["sender"]
            sender_password = self.email_config["password"]
            recipient_email = self.email_config["recipient"]
            
            message = MIMEMultipart()
            message["From"] = sender_email
            message["To"] = recipient_email
            message["Subject"] = f"[Android Automation] {status['status']}"
            
            body = f"""
            Android App Automation Pipeline 결과
            
            상태: {status['status']}
            시간: {status['timestamp']}
            로그: {status['log_file']}
            
            미리보기:
            {status['content_preview']}
            """
            
            message.attach(MIMEText(body, "plain"))
            
            with smtplib.SMTP_SSL("smtp.gmail.com", 465) as server:
                server.login(sender_email, sender_password)
                server.send_message(message)
            
            print("✅ 이메일 알림 전송 완료")
        except Exception as e:
            print(f"❌ 이메일 전송 실패: {e}")
    
    def generate_dashboard_data(self) -> dict:
        """대시보드용 데이터 생성"""
        reports = sorted(self.workspace.glob("automation_report_*.json"), reverse=True)
        
        recent_reports = []
        for report_file in reports[:10]:
            with open(report_file) as f:
                recent_reports.append(json.load(f))
        
        return {
            "total_runs": len(reports),
            "recent_reports": recent_reports,
            "success_rate": f"{(len([r for r in recent_reports if r['stages_completed']['play_store_upload'] == '✅']) / len(recent_reports) * 100):.1f}%"
        }
if __name__ == "__main__":
    email_config = {
        "sender": os.getenv("MONITOR_EMAIL"),
        "password": os.getenv("MONITOR_PASSWORD"),
        "recipient": os.getenv("NOTIFY_EMAIL")
    }
    
    monitor = AutomationMonitor(
        workspace_path="/path/to/android-automation-workspace",
        email_config=email_config
    )
    
    # 최신 로그 확인
    status = monitor.check_latest_log()
    print(json.dumps(status, indent=2))
    
    # 이메일 알림 (필요시)
    if os.getenv("SEND_EMAIL") == "true":
        monitor.send_email_notification(status)
    
    # 대시보드 데이터
    dashboard_data = monitor.generate_dashboard_data()
    print("\n대시보드 데이터:")
    print(json.dumps(dashboard_data, indent=2))

📦 최종 디렉토리 구조

android-automation-workspace/
├── market_research/
│   ├── auto_research.py              # 시장 조사 자동화
│   ├── research_prompt.txt
│   └── report_20250213_*.md          # 생성된 시장 리포트
│
├── app_dev/
│   ├── init_android_project.sh       # Android 프로젝트 초기화
│   ├── create_app_prompt.txt
│   └── MyProductivityApp/            # 생성된 Android 프로젝트
│       ├── app/
│       ├── data/
│       ├── domain/
│       ├── presentation/
│       ├── build.gradle.kts
│       └── gradlew
│
├── metadata/
│   ├── auto_metadata_generator.py    # 메타데이터 자동 생성
│   └── generated/
│       ├── metadata.json
│       ├── PRIVACY_POLICY.md
│       └── TERMS_OF_SERVICE.md
│
├── .github/
│   └── workflows/
│       └── android-ci-cd.yml         # GitHub Actions 워크플로우
│
├── android/
│   └── fastlane/
│       └── Fastfile                  # Fastlane 배포 설정
│
├── scripts/
│   ├── full_automation.py            # 완전 자동화 파이프라인
│   ├── setup_play_store_auth.sh      # Play Store 인증 설정
│   ├── schedule_automation.sh        # 자동화 스케줄링
│   └── monitor_automation.py         # 모니터링 및 알림
│
├── AGENTS.md                         # OpenCode 프로젝트 정보
├── automation_log_*.txt              # 자동화 로그
├── automation_report_*.json          # 최종 리포트
└── README.md                         # 프로젝트 문서

🚀 빠른 시작 가이드

# 1. 작업공간 초기화
cd android-automation-workspace
opencode init
# 2. 완전 자동화 실행
python scripts/full_automation.py internal
# 3. 결과 확인
cat automation_report_*.json | jq .
# 4. GitHub에 푸시 (CI/CD 자동 트리거)
git push origin main

⚠️ 주요 주의사항

  1. Play Store 서비스 계정: 안전하게 저장하고 GitHub Secrets에 등록
  2. 키스토어 관리: 프로덕션 키스토어는 별도로 보관
  3. 배포 정책: 첫 배포는 항상 내부 테스트 → 베타 → 프로덕션 순서
  4. 테스트 커버리지: 최소 70% 이상의 테스트 커버리지 권장
    당신의 고급 기술 스택을 고려했을 때, 이 자동화 파이프라인은 Docker 컨테이너화, Kubernetes 배포, 멀티 환경 관리 등으로 더욱 확장할 수 있습니다. 추가 질문이 있으신가요?
    인용:
    [1] Opencode와 MCP로 앱스 스크립트 자동 개발하기 - 중년코딩 https://wikidocs.net/blog/@JNCD/7158/
    [2] 🚀 Android CI/CD with Azure DevOps | Full Pipeline + Google Play Deployment https://www.youtube.com/watch?v=3Ia6kIVZ92Y
    [3] Upload App to Google Play Console & Run a Closed Test https://applikeweb.com/blog/upload-app-google-play-console-closed-test/
    [4] OpenCode Remote: monitor and control your OpenCode sessions from Android (open source) https://www.reddit.com/r/opencodeCLI/comments/1qz5hor/opencode_remote_monitor_and_control_your_opencode/
    [5] How to Build a CI/CD Pipeline for Android Projects https://blog.jetbrains.com/teamcity/2024/07/cicd-for-android/
    [6] How to automatically upload Android app from Jenkins ... https://stackoverflow.com/questions/21945779/how-to-automatically-upload-android-app-from-jenkins-to-google-play-alpha-test
    [7] Docs https://opencode.ai/docs
    [8] Flutter App Deployment - Leverage Codemagic CD to publish your app to Google Play Store https://www.youtube.com/watch?v=mg8_pM7sGM8
    [9] Automatic publish Beta Android app to the Google Play store https://stackoverflow.com/questions/23427304/automatic-publish-beta-android-app-to-the-google-play-store
    [10] giuliastro/opencode-remote-android https://github.com/giuliastro/opencode-remote-android
    [11] CI/CD, 왜 해야 할까? (ft. GitHub Actions로 완성하는 Android ... https://velog.io/@hearit/CICD-왜-해야-할까-ft.-GitHub-Actions로-완성하는-Android-자동-배포
    [12] Automating Publishing to the Play Store | CodePath Android Cliffnotes https://guides.codepath.com/android/automating-publishing-to-the-play-store
    [13] AppFramework https://opencode.de/en/software/app-framework-4588
    [14] Complete Guide to Android App Publishing in 2025 https://foresightmobile.com/blog/complete-guide-to-android-app-publishing-in-2025
    [15] Automate Publishing your Android app to Google Play Store ... https://www.zone2.tech/blog/play-store-automation
개인정보보호링크