mobile-stores-cicd: Python-тулкит из 65 команд — автоматизация App Store Connect и Google Play | 2026
ИТ

mobile-stores-cicd: Python-тулкит из 65 команд для автоматизации App Store Connect и Google Play

Публикация трёх iOS-приложений и их Android-аналогов через App Store Connect и Google Play — это полноценная операционная работа. Каждый релизный цикл требует ротации сертификатов, перегенерации provisioning-профилей, обновления метаданных на четырёх языках, компоновки скриншотов для пяти семейств устройств, дистрибуции через TestFlight, отправки на App Review, управления поэтапным раскатом и продвижения треков в Play Console. Умножь это на три приложения — два white-label мессенджера на Rocket.Chat и Flutter-инструмент для безопасности — и ручная работа пожирает целые недели. Я создал mobile-stores-cicd, чтобы заменить всё это единым Python CLI, который напрямую общается с Apple и Google API, рендерит метаданные из Jinja2-шаблонов, автоматизирует захват скриншотов на iOS Simulator и Android-эмуляторах, поддерживает sideload IPA с пере-подписанием кода и доставкой через Cloudflare R2, а также включает опциональный Streamlit-дашборд.

В этой статье я разбираю все 65 CLI-команд (48 для iOS, 17 для Android), многоуровневый pipeline метаданных, автоматизацию скриншотов с захватом через Maestro и рендерингом рамок устройств через Pillow, инфраструктуру sideload для пере-подписания сторонних IPA и OTA-публикации, синхронизацию переводов через Crowdin и Streamlit-дашборд, который связывает всё воедино. Включаю реальные примеры конфигурации, реальные рабочие сценарии и скриншоты из трёх продакшн-приложений, которыми тулкит управляет сегодня.

Содержание
  1. Зачем делать собственную автоматизацию сторов вместо Fastlane
  2. Обзор архитектуры
  3. Каталог приложений: три продакшн-приложения, четыре языка
  4. Командная поверхность iOS: 48 команд в 10 доменах
  5. Управление сертификатами и provisioning-профилями
  6. Pipeline метаданных: pull, шаблон, рендер, push
  7. Автоматизация скриншотов: захват, компоновка, валидация, загрузка
  8. Жизненный цикл релиза: от черновика до поэтапного раската
  9. Операции TestFlight
  10. Pipeline sideload: пере-подписание, инъекция твиков и OTA-доставка
  11. Настройка стора: одноразовые формы для обеих платформ
  12. Операционные команды
  13. Android / Google Play: 17 команд для полного цикла релиза
  14. Streamlit-дашборд: операторская консоль
  15. Обзор приложений
  16. Редактор метаданных
  17. Pipeline скриншотов
  18. Переводы
  19. Релизы: iOS и Android
  20. Настройка стора
  21. Консоль sideload
  22. Настройки и окружение
  23. Система шаблонов метаданных в деталях
  24. Перевод и локализация: три параллельные поверхности
  25. Окружение и учётные данные
  26. Модель безопасности и философия dry-run
  27. Тестирование и непрерывная интеграция
  28. Статистика репозитория
  29. Типичные сквозные рабочие процессы
  30. Полный цикл iOS-релиза
  31. Полный цикл Android-релиза
  32. Полный pipeline скриншотов
  33. Цикл синхронизации переводов
  34. Sideload: от GitHub-релиза до OTA-установки
  35. Начало работы
  36. Карта документации
  37. FAQ
  38. Этот тулкит заменяет Fastlane?
  39. Можно использовать на Linux или Windows?
  40. Сколько приложений может обслуживать тулкит?
  41. Что будет, если API App Store Connect изменится?
  42. Безопасно ли использовать sideload pipeline с платным Apple Developer аккаунтом?
  43. Можно использовать для React Native приложений?
  44. Как работает функция AI-перевода?
  45. Что дальше
  46. Нужна консультация?

Зачем делать собственную автоматизацию сторов вместо Fastlane

Архитектурная диаграмма тулкита mobile-stores-cicd

Fastlane — устоявшийся стандарт мобильного CI/CD. Он покрывает огромную площадь: подписание кода, захват скриншотов, загрузка метаданных, дистрибуция через TestFlight и многое другое. Для больших команд с десятками приложений его экосистема плагинов и поддержка сообщества трудно заменимы. Но для маленького портфеля из трёх приложений, которыми управляет один человек, тяжеловесность Fastlane становится проблемой.

Во-первых: Ruby. Весь API App Store Connect уже доступен как REST API с JWT-аутентификацией. Обернуть его в Python-клиент — несколько сотен строк, и ты получаешь прямой контроль над каждым запросом, заголовком и курсором пагинации. Никаких конфликтов версий в Gemfile, никаких сюрпризов с Bundler, никакой зависимости от языка, который ты больше нигде в проекте не используешь.

Во-вторых: структура метаданных. Fastlane предполагает один набор плоских текстовых файлов на каждую локаль для каждого приложения. Мне была нужна система шаблонов, где общий контент (паттерны URL политики конфиденциальности, общий промо-текст) живёт в переиспользуемых шаблонах, специфичные для приложения значения (названия брендов, заметки о версиях) — в YAML на каждое приложение, а движок рендеринга объединяет пять уровней приоритета перед генерацией финальных текстовых файлов для загрузки в App Store Connect. Модель метаданных Fastlane не поддерживает это без внешних скриптов.

В-третьих: компоновка скриншотов. Инструмент snapshot в Fastlane захватывает скриншоты, но компоновка с рамками устройств, локализованными заголовками, градиентными фонами и кастомной типографикой требует frameit или внешних инструментов. Мне был нужен единый pipeline: Maestro-потоки проводят Simulator через каждое состояние приложения, сырые PNG попадают в структурированную директорию, Pillow компонует их с оверлеями рамок и текстом заголовков из YAML по локалям, а финальные изображения загружаются напрямую в ASC через тот же Python-клиент.

Результат — тулкит, который делает меньше, чем Fastlane по абсолютному охвату, но делает это без единой Ruby-зависимости, с полным Python-контролем над каждым API-вызовом, моделью метаданных, спроектированной для переиспользования шаблонов между приложениями, и встроенным sideload pipeline, которого у Fastlane вообще нет.

Обзор архитектуры

Тулкит представляет собой чистый Python-пакет, построенный вокруг двух CLI-точек входа и опционального веб-дашборда:

./apple <subcommand> [flags]     # App Store Connect (48 commands)
./droid <subcommand> [flags]     # Google Play (17 commands)
./launch                          # Interactive menu (questionary-based)
./apple ui                        # Streamlit dashboard

iOS-клиент (ios/client.py) обеспечивает аутентификацию с App Store Connect API с помощью ES256 JWT-токенов, сгенерированных из .p8-ключа. Android-клиент (android/client.py) аутентифицируется через OAuth2-поток сервисного аккаунта Google. Оба клиента реализуют автоматическую пагинацию, обработку rate-limit и структурированные отчёты об ошибках.

Структура репозитория:

ios/                     ASC client, CLI, Keychain helpers, 48 commands
ios/sideload/            IPA re-sign, OTA manifest, R2 publish, Telegram pipeline
ios/ui/                  Streamlit dashboard (8 view modules, ~300KB of UI code)
ios/store_sections/      Store setup form handlers (9 sections)
android/                 Google Play client, CLI, 17 commands
android/commands/        Release, screenshot, metadata, store-config commands
common/                  Shared utilities (Play Store scrape, progress bars)
metadata/                App catalog, templates, per-app values, screenshots
metadata/templates/      Jinja2 templates with locale-specific value layers
.rendered/               Generated metadata payloads ready for push (gitignored)
.cache/                  Local state: UI prefs, ASC baselines, sideload state
secrets/                 Credentials: .p8, .p12, service account JSON (gitignored)
tests/                   28 pytest files for non-network regression coverage
docs/                    11 workflow guides + troubleshooting + UI overview
.github/workflows/       Crowdin sync + full pytest CI

Каталог приложений: три продакшн-приложения, четыре языка

Всё начинается с metadata/apps.yaml. Этот файл связывает каждый slug приложения с его идентификаторами в сторах, конфигурацией сборки, выбором шаблона и платформенно-специфичными настройками. Текущий каталог содержит три продакшн-приложения:

ПриложениеПлатформаФреймворкBundle IDНазначение
VaultApproveriOS + AndroidFluttercom.vaultapprover.app2FA push-подтверждение для Vaultwarden
chat.ilia.aeiOS + AndroidReact Nativechat.ilia.ae / chat.rocket.ilia.aeПерсональный брендированный Rocket.Chat
EL · HubiOS + AndroidReact Nativechat.estateliga.workКорпоративный Rocket.Chat для Estateliga

Все три приложения локализованы на четыре локали: en-US (базовая), ru (русский — русскоязычная аудитория ОАЭ, РФ, КЗ), ar-SA (арабский — ОАЭ и Ближний Восток, RTL), и zh-Hans (упрощённый китайский — материковый Китай). Это значит, что каждая операция с метаданными, компоновка скриншотов и синхронизация переводов проходят одновременно на четырёх языках.

Каждая запись приложения в apps.yaml определяет ASC app ID, bundle ID, отображаемое имя, выбор шаблона, upstream-репозиторий, путь к локальному репозиторию, glob-паттерн для IPA, правила форматирования заметок к релизу (включая многоязычные стоп-листы для фильтрации записей из upstream changelog о функциях, которые брендированные сборки не поставляют), и опциональный блок android: с именем пакета в Play Console, треком релиза по умолчанию, командой Gradle-сборки и путём к AAB.

Командная поверхность iOS: 48 команд в 10 доменах

CLI ./apple содержит 48 подкоманд в 10 функциональных доменах. Каждая деструктивная операция поддерживает флаг --dry-run. Вот полная разбивка:

Управление сертификатами и provisioning-профилями

Ротация сертификатов Apple — самая чреватая ошибками ручная задача в iOS-разработке. Один просроченный сертификат затрагивает каждый provisioning-профиль, который на него ссылается, во всех приложениях. Тулкит автоматизирует полный рабочий процесс ротации:

./apple inventory                    # cross-reference Keychain + local profiles + ASC server
./apple list-certs                   # SHA-1 fingerprints, types, expiry dates
./apple list-profiles --sha1 <OLD>   # find all profiles referencing this cert
./apple revoke-cert --sha1 <OLD> --dry-run
./apple regenerate-profiles \
  --old-sha1 <OLD> --new-sha1 <NEW> \
  --type IOS_APP_STORE --save-local  # re-issue on ASC, save .mobileprovision to Xcode
./apple cleanup --dry-run            # preview stale profile backup + expired cert removal
./apple cleanup                      # execute with backup-first safety

Команда inventory даёт трёхуровневую перекрёстную сверку только для чтения: что установлено в Keychain локально, какие .mobileprovision-файлы есть в директории provisioning-профилей Xcode, и что сообщает ASC на стороне сервера. Команда cleanup сохраняет резервные копии устаревших профилей перед удалением и удаляет истёкшие идентификаторы Keychain, с настраиваемым параметром --keep-keychain-grace-days. Файлы macOS .provisionprofile детектируются, но намеренно не трогаются.

Pipeline метаданных: pull, шаблон, рендер, push

Система метаданных использует Jinja2-шаблоны с пятиуровневым приоритетом значений, что позволяет переиспользовать тексты между приложениями, сохраняя специфичные для приложения значения декларативными:

  1. Общие значения по умолчаниюmetadata/templates/_common/values/<locale>.yaml
  2. Значения шаблонаmetadata/templates/<template>/values/<locale>.yaml
  3. Брендинг приложенияmetadata/<slug>/brand.yaml
  4. Значения релизаmetadata/<slug>/release.yaml
  5. Переопределения по локалямmetadata/<slug>/locales/<locale>.yaml

Рендеринг использует режим StrictUndefined — любая отсутствующая переменная немедленно обрушивает рендер, вместо того чтобы молча генерировать битый текст. После объединения значений файлы шаблонов ищутся в директории шаблонов приложения с фолбэком на _common. Файловые переопределения в metadata/<slug>/overrides/ полностью обходят Jinja-рендеринг для случаев, когда нужен вручную подготовленный текст.

./apple metadata-pull --app el-hub --version latest    # pull live ASC state
./apple metadata-render --app el-hub --show            # render templates → .rendered/
./apple metadata-push --app el-hub --dry-run           # preview field-level diffs
./apple metadata-push --app el-hub                     # PATCH existing, POST new locales
./apple release-notes-fetch --app el-hub --store ios   # seed from GitHub release notes
./apple translate-locale --app el-hub --field release.notes --dry-run  # LLM fan-out

Команда release-notes-fetch подтягивает заметки к релизу из upstream-репозитория на GitHub и записывает их в YAML английской локали. Она применяет правила форматирования для каждого приложения из apps.yaml: удаление строк по паттернам (с многоязычными стоп-листами для функций, которые брендированные сборки не поставляют), текстовые замены и очистку от markdown. Команда translate-locale затем генерирует черновики для целевых локалей через Anthropic API при настроенном ANTHROPIC_API_KEY.

Автоматизация скриншотов: захват, компоновка, валидация, загрузка

Pipeline скриншотов берёт сырые захваты из Simulator и превращает их в готовые для App Store изображения с рамками устройств и локализованными заголовками:

metadata/<slug>/screenshots/
├── _source/                    raw UI captures (iPhone, iPad, per-locale)
│   ├── screens.yaml            screen catalog, devices, styles, frame insets
│   ├── <locale>/              locale-specific iPhone captures
│   └── _ipad/<locale>/        device-specific captures
├── _capture.yaml               capture contract (build, Simulator, locales, modes)
├── _content/                   per-locale headline YAML for composition
├── _frames/                    transparent device frame PNGs
├── _maestro/                   per-mode Maestro flow files
└── <locale>/<device>/         final composed screenshots for ASC upload

Pipeline проходит пять этапов:

  1. Рендеринг рамокframes-render генерирует через Pillow фолбэк-рамки устройств (на данный момент поставляется рендерер APP_IPHONE_69; для других устройств используются вручную подготовленные PNG-рамки)
  2. Захватscreenshots-capture загружает настроенный Simulator, собирает или переиспользует .app, устанавливает его для каждого режима, запускает с переопределениями локали (-AppleLanguages, -AppleLocale), выполняет Maestro-потоки из _maestro/ и копирует PNG в _source/
  3. Компоновкаscreenshots-generate объединяет сырой UI, рамки устройств, локализованный текст заголовков, градиентные фоны и кастомную типографику в финальные изображения для стора в двух стилях: marketing (рамка + заголовок + градиент) и overlay (чистый UI с текстовым оверлеем)
  4. Валидацияscreenshots-validate проверяет структуру папок, покрытие устройств и правила Apple по размерам для каждой локали и семейства устройств
  5. Загрузкаscreenshots-push загружает наборы скриншотов в ASC через multipart S3-загрузку с --replace для перезаписи существующих наборов

Для white-label приложений Rocket.Chat скрипт захвата запускает брендированное приложение с -RCDemoMode <mode>, чтобы скриншоты создавались из повторяемых in-app демо-состояний (splash, auth, rooms, room, profile, settings), а не из дизайн-экспортов. Команда screenshots-snap предоставляет облегчённый путь для пере-захвата одного экрана, когда у тебя уже запущена сессия Simulator.

Текущие наборы скриншотов покрывают пять семейств устройств на приложение: APP_IPHONE_69 (iPhone 16 Pro Max), APP_IPAD_PRO_3GEN_129 (12.9″ iPad Pro), ANDROID_PHONE (Pixel 8), ANDROID_TABLET_10 (Pixel Tablet) и APP_WATCH_SERIES_10 (Apple Watch). Это означает 6 экранов × 4 локали × 5 устройств = 120 скриншотов на приложение, или 360 по всему портфелю.

Жизненный цикл релиза: от черновика до поэтапного раската

Автоматизация релиза покрывает полный путь отправки в App Store:

./apple apps-audit --app el-hub           # full state audit before release
./apple version-create --app el-hub --version 4.71.4 --dry-run
./apple version-create --app el-hub --version 4.71.4
./apple upload-ipa --app el-hub           # upload .ipa to TestFlight processing
./apple testflight-list --app el-hub      # confirm build is ready
./apple build-attach --app el-hub --build-version 23   # attach to draft version
./apple testflight-distribute --app el-hub --dry-run   # push to external groups
./apple version-submit --app el-hub --dry-run          # pre-flight checks
./apple version-submit --app el-hub                     # submit for App Review
./apple phased-release --app el-hub --action status    # monitor rollout
./apple phased-release --app el-hub --action pause     # pause if issues
./apple phased-release --app el-hub --action complete  # push to 100%

Команда version-submit выполняет предпроверки: прикреплена ли сборка, все ли обязательные локализации на месте, есть ли контактные данные для App Review — всё это до отправки. Команда version-delete может удалить устаревшие редактируемые черновики, при этом --force обязателен, если сборка уже прикреплена. Команда apps-audit выводит всё за один раз: метаданные уровня приложения, сущности app info, локализации версий, отсутствующие поля, данные контакта для ревью, прикреплённые сборки и настройки доступности.

Операции TestFlight

Управление TestFlight выходит за рамки простого списка сборок:

  • testflight-list — показывает недавние сборки и бета-группы
  • testflight-invite — создаёт или переиспользует тестировщика и приглашает его в бета-группу
  • testflight-distribute — отправляет новейшую валидную сборку во внешние группы и опционально отправляет на Beta Review
  • testflight-expire — деактивирует старые сборки, сохраняя N последних обработанных сборок доступными

Pipeline sideload: пере-подписание, инъекция твиков и OTA-доставка

Подсистема sideload обеспечивает установку сторонних iOS-приложений на физический iPhone без джейлбрейка: управление сертификатами, регистрация устройств, пере-подписание IPA, публикация OTA-манифестов в Cloudflare R2 и опциональная доставка через Telegram. Работает через App Store Connect API и стандартные инструменты Apple Developer.

./apple sideload-init --email you@example.com         # issue signing cert
./apple sideload-import-cert --p12 ~/cert.p12         # or import existing
./apple sideload-register-device                       # auto-detect USB iPhone
./apple sideload-install \
  --repo https://github.com/SoCuul/SCInsta \
  --refresh-altsource                                  # fetch, re-sign, publish
./apple sideload-build-gh \
  --upstream owner/repo --fork user/fork               # trigger forked CI build
./apple sideload-list                                  # cert, device, published apps
./apple sideload-remove --bundle-id com.example.app    # unpublish from R2
./apple sideload-prune --dry-run                       # clean orphaned R2 objects
./apple sideload-tg-init                               # capture Telegram chat_id

Команда sideload-install делает всё за один вызов: скачивает IPA из GitHub-релиза или по прямой ссылке, создаёт или переиспользует необходимый bundle ID и provisioning-профиль в ASC, пере-подписывает приложение через zsign, загружает подписанный IPA и OTA-манифест в Cloudflare R2, выводит URL для установки и опционально отправляет ссылку с QR-кодом в Telegram. Тип сертификата имеет значение: сертификаты DEVELOPMENT ведут к профилям IOS_APP_DEVELOPMENT (требуется однократное подтверждение доверия на устройстве), тогда как сертификаты DISTRIBUTION ведут к профилям IOS_APP_ADHOC для более чистой OTA-установки.

Команда sideload-build-gh идёт дальше: она запускает сборку в форкнутом GitHub Actions, ждёт артефакт, скачивает результат, пере-подписывает и публикует IPA. Для приложений, которые это поддерживают, опциональная интеграция с cyan (pyzule-rw) может инжектить .deb-твик в расшифрованный базовый IPA перед пере-подписанием — полезно для кастомизированных сборок Instagram или Spotify.

Локальное состояние хранится в .cache/sideload_state.json (ID сертификата, зарегистрированные устройства, опубликованные приложения, Telegram chat ID), материалы для подписи в secrets/apple/sideload/, а временные рабочие файлы в .cache/sideload/. Всё, кроме локального состояния sideload, добавлено в gitignore.

Настройка стора: одноразовые формы для обеих платформ

App Store Connect и Google Play имеют формы конфигурации, которые редко меняются после начальной настройки: декларации приватности, возрастные рейтинги, учётные данные для ревьюера, ценообразование, доступность по территориям, обоснования разрешений, флаги соответствия и настройки монетизации. Команды store-config-* управляют ими декларативно:

./apple store-config-pull --app vaultapprover           # pull live ASC state
./apple store-config-verify --app vaultapprover --strict # diff local vs live
./apple store-config-push --app vaultapprover --section privacy --dry-run
./apple store-config-clone --from chat-ilia --to el-hub --dry-run  # copy config

Конфигурация стора каждого приложения хранится в metadata/<slug>/store-config.yaml, сгенерированном из дефолтов шаблона плюс переопределений для конкретного приложения. YAML-схема покрывает девять секций: app_identity, age_rating, privacy (с полными декларациями сбора данных, которые питают и Play Data Safety, и ASC App Privacy), reviewer_access (с тестовыми учётными данными из gitignored-файла secrets/reviewer-creds.yaml), pricing_and_availability, permissions, compliance, monetization и assets. Те же команды работают и для ./apple (ASC), и для ./droid (Play Console).

Операционные команды

Повседневные операции покрыты выделенными командами:

  • sales-report [--days N] [--frequency DAILY|WEEKLY|MONTHLY] — получение и агрегация отчётов о продажах из App Store Connect
  • reviews-list --app <slug> [--unanswered] — список отзывов пользователей с состоянием ответов
  • reviews-respond --review-id <id> --text <response> — публикация ответа разработчика
  • app-strings-sync --app <slug> --direction push|pull — зеркалирование in-app строк между репозиторием приложения и Crowdin
  • translate-locale --app <slug> --field <field> — AI-генерация черновиков локализации через Anthropic

Android / Google Play: 17 команд для полного цикла релиза

CLI ./droid — это Google Play аналог ./apple. Он использует тот же каталог приложений, шаблоны метаданных и структуру директорий для скриншотов, но работает с Google Play Developer API через сервисный аккаунт:

./droid apps-list                                      # Android-enabled apps
./droid metadata-push --app vaultapprover --dry-run    # render + push Play listings
./droid screenshots-capture --app vaultapprover --locale en-US --build
./droid screenshots-capture-rn --app chat-ilia --locales en-US,ru --build
./droid screenshots-push --app vaultapprover --replace
./droid build --app vaultapprover                      # flutter build appbundle
./droid release --app vaultapprover --track internal --dry-run
./droid tracks-list --app vaultapprover
./droid promote --app vaultapprover --track production --status completed
./droid release-delete --app vaultapprover --track internal --dry-run
./droid reviews-list --app vaultapprover --limit 20

Команда screenshots-capture-rn — оптимизированный путь захвата для React Native white-label приложений: она собирает один демо-APK один раз, а затем перезапускает его с разными mode или locale extras для захвата всех экранов без пересборки. Команда release загружает AAB и создаёт релиз на выбранном Play-треке (internal, alpha, beta или production). Команда promote продвигает существующий релиз трека без принудительной новой загрузки. Команда release-delete очищает черновые или незавершённые релизы из трека, не трогая завершённые живые релизы — страховка от устаревших черновиков, блокирующих новые загрузки.

Конфигурационная поверхность стора полностью зеркалит iOS: store-config-pull, store-config-push, store-config-verify и store-config-clone работают с Play Console для деклараций приватности, возрастных рейтингов, обоснований разрешений и доступности по территориям.

Streamlit-дашборд: операторская консоль

Запуск ./apple ui поднимает Streamlit-дашборд, который оборачивает CLI в веб-интерфейс. Он следует цветовой схеме браузера (светлая и тёмная темы), сохраняет настройки UI локально и поддерживает общий контекст приложения, где каждая вкладка работает с одним и тем же выбранным приложением и платформой. Это не замена CLI. Он оборачивает те же команды и добавляет визуальную обратную связь там, где это действительно полезно.

Анимированная демонстрация Streamlit-дашборда mobile-stores-cicd с управлением приложением VaultApprover: обзор приложений, редактирование метаданных, pipeline скриншотов, переводы, релизы, настройка стора, sideload и настройки
Живая демонстрация дашборда с VaultApprover в качестве рабочего приложения — прокрутка по всем восьми вкладкам

Обзор приложений

Вкладка каталога приложений в дашборде mobile-stores-cicd: выбран VaultApprover с bundle ID, ASC ID, привязкой шаблонов и статусом синхронизации

Вкладка Apps показывает полный каталог из metadata/apps.yaml рядом с профилем текущего выбранного приложения. Она отображает bundle ID, ASC app ID, выбор шаблона, привязку к репозиторию, состояние git, наличие локальной сборки и временные метки последней синхронизации. Быстрые действия запускают apps-audit, pull/push метаданных и dry-run загрузки скриншотов прямо из интерфейса. Левый сайдбар — это постоянный операционный контекст: рабочее приложение, активная платформа (с зелёными «Android» и синими «iOS» пиллами), заметки, текущее состояние версии, локальные ресурсы, история синхронизации и семейства устройств.

Редактор метаданных

Вкладка редактора метаданных: iOS-метаданные VaultApprover с описанием, ключевыми словами, промо-текстом и превью скриншотов под разные устройства

Повседневное редактирование метаданных происходит здесь. Ты получаешь превью скриншотов с учётом устройства (переключение между iPhone, iPad и Android), информацию для ревью, сравнение отрендеренного текста и переключение локалей. Предупреждения об устаревшей синхронизации и состояние версии всегда видны, чтобы ты понимал, нужен ли metadata-push.

Pipeline скриншотов

Вкладка pipeline скриншотов: сводка покрытия, сырые захваты, финальные скомпонованные скриншоты и кнопки действий для VaultApprover

Вкладка Screenshots показывает сводку покрытия (семейства устройств × локали), состояние готовности пакета и действия pipeline в один клик: захват, генерация, валидация, загрузка. И сырые захваты, и финальные скомпонованные скриншоты можно просматривать в одном представлении, чтобы отловить устаревшие результаты до загрузки. YAML с локализованными заголовками тоже можно редактировать прямо здесь.

Переводы

Вкладка переводов: покрытие локалей по ASC-метаданным, заголовкам скриншотов и in-app строкам для VaultApprover

Вкладка Translations агрегирует покрытие переводов по трём параллельным поверхностям: метаданные App Store Connect (значения YAML по локалям), текст заголовков скриншотов (YAML из _content/) и in-app строки (app-strings/ для приложений, подключённых через app-strings-sync). Она предоставляет действия pull, рендер шаблонов и push для текстов стора, плюс Crowdin-backed push и pull для in-app строк. Если у локали отсутствует поле или она отстаёт от английского исходника — ты увидишь это здесь.

Релизы: iOS и Android

Вкладка iOS-релизов: поиск сборок в TestFlight, статус загрузки и управление прикреплением сборок для VaultApprover

На iOS вкладка Releases — это поверхность подготовки сборок: поиск сборок в TestFlight, проверка статуса загрузки, прикрепление сборки к редактируемой App Store-версии и управление отправкой.

Вкладка Android-релизов: состояние треков Play Console, статус покрытия и управление релизами для VaultApprover

Та же вкладка переключается на Android-операции релиза при переключении платформы. Она показывает состояние треков Play Console, статус покрытия и действия по синхронизации метаданных/строк, необходимые перед раскатом. Ты остаёшься в том же контексте приложения; меняются только платформенно-специфичные элементы управления.

Настройка стора

Вкладка настройки стора: девять секций конфигурации для VaultApprover с действиями pull, verify и clone

Store Setup покрывает одноразовую или редко изменяемую конфигурацию: идентичность, возрастной рейтинг, декларации приватности, доступ для ревьюера, ценообразование и доступность, разрешения, соответствие, монетизация и маркетинговые ассеты. Он отслеживает свежесть кэша для каждой секции и поддерживает потоки клонирования от смежного приложения, что экономит реальное время при подключении нового приложения, которое разделяет большинство настроек с существующим.

Консоль sideload

Вкладка sideload: статус сертификата подписи, зарегистрированные устройства, каталог опубликованных приложений и действия подписания и публикации

Вкладка Sideload показывает статус сертификата подписи, зарегистрированные устройства и каталог опубликованных sideload-приложений. Каждая запись каталога имеет сгруппированные действия «Sign & publish». Sideload не привязан к App Store Connect или Google Play, но наличие его в том же дашборде избавляет от переключения контекста, когда ты единственный оператор.

Настройки и окружение

Вкладка настроек: переменные окружения, статус тулчейна, предпосылки для sideload и локальные проверки готовности

Вкладка Settings — место, где ты подтверждаешь, что всё правильно подключено: предпосылки для sideload, количество опубликованных приложений, доступность учётных данных и готовность локального тулчейна. Полезно, когда что-то ломается и нужна быстрая диагностика без перехода в терминал.

Система шаблонов метаданных в деталях

Модель метаданных управляет листингами как в iOS, так и в Android сторах. Она работает по принципу CSS-специфичности: более конкретные значения перекрывают менее конкретные, а файловые переопределения обходят всё остальное.

Рассмотрим конкретный пример. Общий шаблон предоставляет фолбэк-файл description.txt.j2, использующий {{ description }}. Шаблон chat-shared предоставляет более богатую версию, интерполирующую {{ app_name }}, {{ chat_backend_url }} и списки функций. Файл brand.yaml каждого приложения задаёт app_name и chat_backend_url. Файл <locale>.yaml каждой локали предоставляет переведённый список функций.

Выходные поля покрывают и App Info (название, подзаголовок, URL политики конфиденциальности, текст политики конфиденциальности, URL выбора конфиденциальности), и Version Localization (описание, ключевые слова, промо-текст, что нового, URL поддержки, маркетинговый URL). Команда metadata-push сравнивает отрендеренный результат поле-за-полем с текущими значениями в App Store Connect и отправляет PATCH-запросы только для изменённых полей или POST-запросы для новых локализаций.

Для Android те же YAML-значения рендерятся через параллельный путь в команде ./droid metadata-push. Play Console API имеет другую модель полей (краткое описание, полное описание, название), но pipeline рендеринга маппит из того же исходного YAML. Это значит, что одна правка в файле локали пропагируется в оба стора.

Перевод и локализация: три параллельные поверхности

Тулкит управляет переводами по трём независимым поверхностям, которые все подключены к Crowdin:

ПоверхностьИсходный путьПотребитель
Метаданные App Storemetadata/<slug>/locales/<locale>.yamlmetadata-rendermetadata-push
Заголовки скриншотовmetadata/<slug>/screenshots/_content/<locale>.yamlscreenshots-generate
In-app строкиmetadata/<slug>/app-strings/<locale>.arbapp-strings-sync → репозиторий приложения

Интеграция с Crowdin работает через GitHub Action (.github/workflows/crowdin-sync.yml), который поддерживает четыре режима: upload (отправка только исходников), download (скачивание завершённых переводов), both (загрузка, затем скачивание), и seed-translations (загрузка существующих переводов из репозитория как одобренного начального контента с --auto-approve-imported). Ночное расписание в 03:00 UTC скачивает завершённые переводы и открывает PR с заголовком chore(l10n): sync translations from Crowdin.

Для приложений, определяющих блок app_strings: в apps.yaml, команда app-strings-sync зеркалирует файлы локализации между репозиторием приложения (например, ~/CODE/vaultwarden/lib/l10n/app_en.arb) и metadata/<slug>/app-strings/. Направление --direction push копирует из репозитория приложения в дерево метаданных (для загрузки в Crowdin), а --direction pull копирует переведённые файлы обратно (после скачивания из Crowdin). Флаги --upload и --download опционально запускают синхронизацию с Crowdin CLI в рамках той же команды.

AI-команда translate-locale предоставляет быстрый путь черновой подготовки, когда сроки Crowdin слишком долгие. Она читает английский исходник из locales/en-US.yaml, отправляет его в Anthropic API с инструкциями для конкретной локали и записывает черновые переводы в файлы целевой локали. Эти черновики предназначены для ручной проверки, а не для слепой публикации — флаг --dry-run показывает предлагаемый текст без записи.

Окружение и учётные данные

Тулкит использует 17 переменных окружения, настраиваемых через файл .env:

ПеременнаяОбязательнаНазначение
ASC_KEY_IDiOS APIID ключа App Store Connect API
ASC_ISSUER_IDiOS APIUUID издателя App Store Connect
ASC_KEY_PATHiOS APIПуть к файлу .p8-ключа
APPLE_TEAM_IDРекомендуетсяИдентификатор Apple-команды
ASC_VENDOR_NUMBERsales-reportНомер вендора для финансовых отчётов
GOOGLE_PLAY_SA_PATHAndroidПуть к JSON сервисного аккаунта Google Play
CROWDIN_PROJECT_IDПереводыID проекта Crowdin
CROWDIN_API_TOKENПереводыПерсональный токен доступа Crowdin
ANTHROPIC_API_KEYAI-переводАктивирует LLM-генерацию черновиков локалей
R2_ACCOUNT_IDSideloadАккаунт Cloudflare для хостинга IPA
R2_ACCESS_KEY_IDSideloadКлюч доступа R2 S3
R2_SECRET_ACCESS_KEYSideloadСекретный ключ R2 S3
R2_BUCKETSideloadИмя бакета R2
R2_PUBLIC_BASE_URLSideloadПубличный HTTPS URL для OTA-загрузок
GITHUB_TOKENОпциональноПовышает лимиты GitHub API
TELEGRAM_BOT_TOKENОпциональноДоставка ссылок для установки sideload
SIDELOAD_DISABLE_TELEGRAMОпциональноОтключает Telegram без удаления состояния

Учётные данные следуют строгой модели локальности: .env, secrets/, .rendered/, .backups/ и .venv/ — все в gitignore. Файл SECURITY.md документирует политику обращения с учётными данными и процедуру реагирования на инциденты утечки.

Модель безопасности и философия dry-run

Каждая команда, изменяющая удалённое состояние (отзыв сертификатов, push метаданных, загрузка скриншотов, создание версий, прикрепление сборок, отправка на ревью, управление поэтапным раскатом, продвижение треков Play, публикация sideload), поддерживает флаг --dry-run, который выводит ровно то, что произойдёт, без выполнения. Я запускаю --dry-run первым каждый раз, даже для операций, которые делал сотни раз.

Дополнительные меры безопасности:

  • cleanup сохраняет резервные копии устаревших provisioning-профилей перед удалением из директории профилей Xcode
  • metadata-push пропускает перезапись непустых значений ASC пустыми отрендеренными значениями
  • version-delete требует --force для удаления черновика с прикреплённой сборкой
  • release-delete (Android) отказывается трогать завершённые живые релизы — очищать можно только черновые и незавершённые релизы
  • ключи подписи sideload и импортированные .p12-бандлы хранятся в secrets/apple/sideload/ и никогда не покидают локальную машину
  • файлы macOS .provisionprofile детектируются при cleanup, но намеренно не трогаются
  • директория .cache/ содержит только локальное состояние: настройки UI, базовые линии ASC и состояние pipeline sideload

Тестирование и непрерывная интеграция

Набор тестов содержит 28 файлов pytest, покрывающих регрессионные пути без сетевых вызовов:

  • Определения display-type скриншотов и правила размеров Apple
  • Хелперы генерации скриншотов, расчёты package-status и source extras
  • Вывод device-family при pull метаданных и хелперы сохранения/удаления YAML по локалям
  • Парсинг каталога Android, рендеринг метаданных Play и проверки Play-валидатора
  • Фильтрация заметок к релизу, утилиты release-хелперов и удобная обработка ошибок Play
  • Хелперы валидации ASC-метаданных и кэширование базовых линий ASC
  • Патч favicon для Streamlit, бутстрап UI-команд и персистентность настроек
  • Персистентность sync-state и определение семейств устройств
  • Автоопределение при создании версий и проверки безопасности удаления версий
  • Smoke-тест Streamlit на уровне браузера (опционально через Playwright)

GitHub Actions запускает полный набор pytest плюс отдельный smoke-тест Streamlit на уровне браузера. conftest.py строит изолированные временные структуры репозитория и подменяет пути к каталогу или состоянию через monkeypatch, чтобы тесты никогда не трогали реальное дерево метаданных. Все тесты локальны и детерминированы — никаких зависимостей от живого App Store Connect или сети в пути по умолчанию.

Статистика репозитория

Репозиторий в цифрах:

МетрикаЗначение
Всего CLI-команд65 (48 iOS + 17 Android)
Основной языкPython (1.1M+ байт)
Вспомогательные языкиShell (5.5KB), Jinja2 (1.6KB)
Код iOS UI~300KB в 8 Streamlit-модулях
Тестовые файлы28 файлов pytest
Файлы документации21 (README + 11 docs + API setup + Android + contributing + security + changelog + crowdin)
Управляемые приложения3 (VaultApprover, chat.ilia.ae, EL·Hub)
Целевые локали4 (en-US, ru, ar-SA, zh-Hans)
Семейства устройств5 (iPhone 16 Pro Max, iPad Pro 12.9″, Pixel 8, Pixel Tablet, Apple Watch S10)
Скриншотов на приложение~120 (6 экранов × 4 локали × 5 устройств)
Секций store-config9 (identity, age, privacy, reviewer, pricing, permissions, compliance, monetization, assets)
GitHub Actions workflows2 (тесты + синхронизация Crowdin)

Типичные сквозные рабочие процессы

Ниже четыре рабочих процесса, которые я регулярно выполняю. Каждый — последовательность CLI-команд, которую можно скопировать и адаптировать под свои приложения.

Полный цикл iOS-релиза

# 1. Audit current state
./apple apps-audit --app el-hub

# 2. Create draft version
./apple version-create --app el-hub --version 4.71.4

# 3. Refresh release notes from GitHub
./apple release-notes-fetch --app el-hub --store ios
./apple translate-locale --app el-hub --field release.notes

# 4. Push metadata and screenshots
./apple metadata-push --app el-hub
./apple screenshots-validate --app el-hub
./apple screenshots-push --app el-hub --replace

# 5. Upload IPA and attach build
./apple upload-ipa --app el-hub
./apple testflight-list --app el-hub
./apple build-attach --app el-hub --build-version 23

# 6. Distribute to testers
./apple testflight-distribute --app el-hub

# 7. Submit for review
./apple version-submit --app el-hub

# 8. Control phased rollout
./apple phased-release --app el-hub --action status
./apple phased-release --app el-hub --action complete

Полный цикл Android-релиза

# 1. Build the AAB
./droid build --app vaultapprover

# 2. Push metadata
./droid metadata-push --app vaultapprover

# 3. Capture and push screenshots
./droid screenshots-capture --app vaultapprover --build
./droid screenshots-push --app vaultapprover --replace

# 4. Create release on internal track
./droid release --app vaultapprover --track internal

# 5. Verify track state
./droid tracks-list --app vaultapprover

# 6. Promote to production
./droid promote --app vaultapprover --track production --status completed

Полный pipeline скриншотов

# 1. Prepare device frames
./apple frames-render --app vaultapprover

# 2. Capture raw UI from Simulator
./apple screenshots-capture --app vaultapprover --build \
  --modes main,lock,setup,totp \
  --devices APP_IPHONE_69,APP_IPAD_PRO_3GEN_129

# 3. Compose final screenshots with frames and headlines
./apple screenshots-generate --app vaultapprover

# 4. Validate dimensions and coverage
./apple screenshots-validate --app vaultapprover

# 5. Upload to App Store Connect
./apple screenshots-push --app vaultapprover --replace

# 6. Android screenshots
./droid screenshots-capture --app vaultapprover --build
./droid screenshots-push --app vaultapprover --replace

Цикл синхронизации переводов

# 1. Push source strings to Crowdin (via GitHub Action or CLI)
./apple app-strings-sync --app vaultapprover --direction push --upload

# 2. Translators work in Crowdin...

# 3. Download completed translations
./apple app-strings-sync --app vaultapprover --direction pull --download

# 4. Re-render store metadata
./apple metadata-render --app vaultapprover
./apple metadata-push --app vaultapprover

# 5. Regenerate screenshots with new headlines
./apple screenshots-generate --app vaultapprover
./apple screenshots-push --app vaultapprover --replace

Sideload: от GitHub-релиза до OTA-установки

# 1. One-time setup
./apple sideload-init --email you@example.com --type DISTRIBUTION
./apple sideload-register-device
./apple sideload-tg-init

# 2. Publish a third-party app
./apple sideload-install \
  --repo https://github.com/SoCuul/SCInsta \
  --refresh-altsource

# 3. Trigger a forked CI build
./apple sideload-build-gh \
  --upstream whoeevee/EeveeSpotify \
  --fork your-user/EeveeSpotify \
  --refresh-altsource

# 4. Manage published apps
./apple sideload-list
./apple sideload-remove --bundle-id com.example.app
./apple sideload-prune --dry-run

Начало работы

Тулкит требует macOS (интеграция с Keychain и Xcode), Python 3.10+ и доступ к целевой команде App Store Connect. Опциональные зависимости открывают дополнительные возможности: Xcode и Simulator для захвата скриншотов, maestro для автоматизированных потоков захвата, zsign для пере-подписания IPA, libimobiledevice для определения USB-устройств, crowdin CLI для локальной синхронизации переводов, streamlit для дашборда и бакет Cloudflare R2 для хостинга sideload.

# 1. Create the environment
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install -r requirements-dev.txt
cp .env.example .env

# 2. Configure credentials
# Edit .env with ASC_KEY_ID, ASC_ISSUER_ID, ASC_KEY_PATH
# See ios/API_KEY_SETUP.md for App Store Connect API key creation
# See android/SERVICE_ACCOUNT_SETUP.md for Google Play

# 3. Verify the setup
./apple apps-list --no-server    # catalog parses
./apple list-certs               # ASC auth works
./droid apps-list                # Android catalog
./launch                         # interactive menu

# 4. Optional: run the test suite
./.venv/bin/python -m pytest -q

Полное руководство по запуску с каждой переменной окружения, шагами первоначальной проверки и устранением типичных ошибок доступно в docs/SETUP.md.

Карта документации

Репозиторий включает 21 файл документации, покрывающий каждый аспект тулкита:

ДокументОбласть
docs/SETUP.mdПолная настройка окружения с нуля
docs/METADATA_WORKFLOW.mdМодель рендеринга/pull/push и приоритет файлов
docs/SCREENSHOTS_WORKFLOW.mdЗахват, компоновка, валидация, загрузка
docs/SIDELOAD_WORKFLOW.mdИнициализация сертификатов, пере-подписание, R2-публикация, Telegram
docs/RELEASE_WORKFLOW.mdСоздание черновика, прикрепление сборки, отправка, поэтапный раскат
docs/STORE_FORMS_DETAILED.mdYAML-схема store-config и дизайн форм
docs/UI_OVERVIEW.mdПолный обзор дашборда по вкладкам
docs/TESTING.mdНастройка pytest, области тестирования, цикл разработки
docs/TROUBLESHOOTING.mdТипичные ошибки ASC, Keychain, шаблонов и Crowdin
CROWDIN_SETUP.mdПроект Crowdin, GitHub Action, поток начального наполнения
android/README.mdКаталог Google Play, CLI и обзор рабочих процессов

FAQ

Этот тулкит заменяет Fastlane?

Для небольшого портфеля приложений, которым управляет один оператор — да. Он покрывает управление сертификатами, шаблонизацию метаданных, автоматизацию скриншотов, отправку релизов и sideloading — всё без Ruby-зависимостей. Для больших команд с десятками приложений и сложными CI pipeline экосистема плагинов и поддержка сообщества Fastlane по-прежнему имеют преимущества.

Можно использовать на Linux или Windows?

Тулкит ориентирован на macOS, потому что интегрируется с Keychain и директориями provisioning-профилей Xcode. Android-часть CLI (./droid) и шаблонизация метаданных работают на любой платформе, но iOS-команды требуют macOS для операций с сертификатами и профилями. Захват скриншотов требует Xcode Simulator (только macOS).

Сколько приложений может обслуживать тулкит?

Жёсткого ограничения нет. Каталог приложений (metadata/apps.yaml) принимает любое количество записей. Текущий deployment управляет тремя приложениями на двух платформах, четырёх локалях и пяти семействах устройств. Для добавления нового приложения нужна одна YAML-запись и соответствующие директории шаблона, локалей и скриншотов.

Что будет, если API App Store Connect изменится?

ASC-клиент (ios/client.py) работает напрямую с REST API Apple с явными путями версий. Изменения API проявляются как HTTP-ошибки, а не как молчаливые сбои. Руководство по устранению неполадок покрывает типичные ошибки аутентификации и авторизации.

Безопасно ли использовать sideload pipeline с платным Apple Developer аккаунтом?

Pipeline sideload создаёт стандартные ASC bundle ID и provisioning-профили — те же объекты, которые создаёт Xcode. Тип сертификата (Development vs Distribution) влияет на тип профиля и опыт установки. Материалы для подписи остаются локально, а команда sideload-prune очищает осиротевшие облачные объекты. Оценка условий обслуживания Apple в отношении sideloading — ответственность пользователя.

Можно использовать для React Native приложений?

Два из трёх управляемых приложений (chat.ilia.ae и EL·Hub) — сборки React Native. Команда screenshots-capture-rn специально спроектирована для RN white-label приложений: она собирает один демо-APK один раз, а затем перезапускает его с разными mode или locale extras для захвата всех скриншотов без пересборки. Контракт _capture.yaml поддерживает RN-специфичные флаги демо-режима типа -RCDemoMode.

Как работает функция AI-перевода?

Команда translate-locale читает английское исходное поле из locales/en-US.yaml, отправляет его в Anthropic API с инструкциями форматирования для конкретной локали и записывает черновые переводы в файлы целевой локали. Она спроектирована как помощник в подготовке черновиков, а не замена профессиональному переводу. Флаг --dry-run показывает предлагаемый текст без записи. Anthropic SDK намеренно не включён в requirements.txt — установи его отдельно, если хочешь эту функцию.

Что дальше

Тулкит работает в продакшне для трёх приложений на двух платформах. iOS-поверхность глубже, но автоматизация Google Play теперь покрывает полный цикл релиза через ./droid. Над чем я работаю дальше: полный паритет функций Android с iOS (Play Developer Reporting API для установок, рейтингов и крашей), CI-рабочие процессы для lint и проверки типов, и упаковка как устанавливаемый Python-дистрибутив.

Исходный код размещён на github.com/ilia-ae/mobile-stores-cicd. Репозиторий приватный — доступ предоставляется по запросу. Если хочешь изучить код или оценить тулкит для своего мобильного CI/CD, напиши и опиши свой кейс.

По вопросам консалтинга по автоматизации мобильного CI/CD, интеграции с App Store Connect или созданию кастомного инструментария управления сторами для твоего портфеля приложений — свяжись со мной.

Нужна консультация?

Если вам нужна профессиональная экспертиза — запишитесь на бесплатную 15-минутную консультацию.

Оцените статью