Публикация трёх 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-дашборд, который связывает всё воедино. Включаю реальные примеры конфигурации, реальные рабочие сценарии и скриншоты из трёх продакшн-приложений, которыми тулкит управляет сегодня.
- Зачем делать собственную автоматизацию сторов вместо Fastlane
- Обзор архитектуры
- Каталог приложений: три продакшн-приложения, четыре языка
- Командная поверхность iOS: 48 команд в 10 доменах
- Управление сертификатами и provisioning-профилями
- Pipeline метаданных: pull, шаблон, рендер, push
- Автоматизация скриншотов: захват, компоновка, валидация, загрузка
- Жизненный цикл релиза: от черновика до поэтапного раската
- Операции TestFlight
- Pipeline sideload: пере-подписание, инъекция твиков и OTA-доставка
- Настройка стора: одноразовые формы для обеих платформ
- Операционные команды
- Android / Google Play: 17 команд для полного цикла релиза
- Streamlit-дашборд: операторская консоль
- Обзор приложений
- Редактор метаданных
- Pipeline скриншотов
- Переводы
- Релизы: iOS и Android
- Настройка стора
- Консоль sideload
- Настройки и окружение
- Система шаблонов метаданных в деталях
- Перевод и локализация: три параллельные поверхности
- Окружение и учётные данные
- Модель безопасности и философия dry-run
- Тестирование и непрерывная интеграция
- Статистика репозитория
- Типичные сквозные рабочие процессы
- Полный цикл iOS-релиза
- Полный цикл Android-релиза
- Полный pipeline скриншотов
- Цикл синхронизации переводов
- Sideload: от GitHub-релиза до OTA-установки
- Начало работы
- Карта документации
- FAQ
- Этот тулкит заменяет Fastlane?
- Можно использовать на Linux или Windows?
- Сколько приложений может обслуживать тулкит?
- Что будет, если API App Store Connect изменится?
- Безопасно ли использовать sideload pipeline с платным Apple Developer аккаунтом?
- Можно использовать для React Native приложений?
- Как работает функция AI-перевода?
- Что дальше
- Нужна консультация?
Зачем делать собственную автоматизацию сторов вместо Fastlane

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 | Назначение |
|---|---|---|---|---|
| VaultApprover | iOS + Android | Flutter | com.vaultapprover.app | 2FA push-подтверждение для Vaultwarden |
| chat.ilia.ae | iOS + Android | React Native | chat.ilia.ae / chat.rocket.ilia.ae | Персональный брендированный Rocket.Chat |
| EL · Hub | iOS + Android | React Native | chat.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-шаблоны с пятиуровневым приоритетом значений, что позволяет переиспользовать тексты между приложениями, сохраняя специфичные для приложения значения декларативными:
- Общие значения по умолчанию —
metadata/templates/_common/values/<locale>.yaml - Значения шаблона —
metadata/templates/<template>/values/<locale>.yaml - Брендинг приложения —
metadata/<slug>/brand.yaml - Значения релиза —
metadata/<slug>/release.yaml - Переопределения по локалям —
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 проходит пять этапов:
- Рендеринг рамок —
frames-renderгенерирует через Pillow фолбэк-рамки устройств (на данный момент поставляется рендерерAPP_IPHONE_69; для других устройств используются вручную подготовленные PNG-рамки) - Захват —
screenshots-captureзагружает настроенный Simulator, собирает или переиспользует.app, устанавливает его для каждого режима, запускает с переопределениями локали (-AppleLanguages,-AppleLocale), выполняет Maestro-потоки из_maestro/и копирует PNG в_source/ - Компоновка —
screenshots-generateобъединяет сырой UI, рамки устройств, локализованный текст заголовков, градиентные фоны и кастомную типографику в финальные изображения для стора в двух стилях:marketing(рамка + заголовок + градиент) иoverlay(чистый UI с текстовым оверлеем) - Валидация —
screenshots-validateпроверяет структуру папок, покрытие устройств и правила Apple по размерам для каждой локали и семейства устройств - Загрузка —
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 Reviewtestflight-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 Connectreviews-list --app <slug> [--unanswered]— список отзывов пользователей с состоянием ответовreviews-respond --review-id <id> --text <response>— публикация ответа разработчикаapp-strings-sync --app <slug> --direction push|pull— зеркалирование in-app строк между репозиторием приложения и Crowdintranslate-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. Он оборачивает те же команды и добавляет визуальную обратную связь там, где это действительно полезно.

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

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

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

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

Вкладка 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 вкладка Releases — это поверхность подготовки сборок: поиск сборок в TestFlight, проверка статуса загрузки, прикрепление сборки к редактируемой App Store-версии и управление отправкой.

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

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

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

Вкладка 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 Store | metadata/<slug>/locales/<locale>.yaml | metadata-render → metadata-push |
| Заголовки скриншотов | metadata/<slug>/screenshots/_content/<locale>.yaml | screenshots-generate |
| In-app строки | metadata/<slug>/app-strings/<locale>.arb | app-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_ID | iOS API | ID ключа App Store Connect API |
ASC_ISSUER_ID | iOS API | UUID издателя App Store Connect |
ASC_KEY_PATH | iOS API | Путь к файлу .p8-ключа |
APPLE_TEAM_ID | Рекомендуется | Идентификатор Apple-команды |
ASC_VENDOR_NUMBER | sales-report | Номер вендора для финансовых отчётов |
GOOGLE_PLAY_SA_PATH | Android | Путь к JSON сервисного аккаунта Google Play |
CROWDIN_PROJECT_ID | Переводы | ID проекта Crowdin |
CROWDIN_API_TOKEN | Переводы | Персональный токен доступа Crowdin |
ANTHROPIC_API_KEY | AI-перевод | Активирует LLM-генерацию черновиков локалей |
R2_ACCOUNT_ID | Sideload | Аккаунт Cloudflare для хостинга IPA |
R2_ACCESS_KEY_ID | Sideload | Ключ доступа R2 S3 |
R2_SECRET_ACCESS_KEY | Sideload | Секретный ключ R2 S3 |
R2_BUCKET | Sideload | Имя бакета R2 |
R2_PUBLIC_BASE_URL | Sideload | Публичный 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-профилей перед удалением из директории профилей Xcodemetadata-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-config | 9 (identity, age, privacy, reviewer, pricing, permissions, compliance, monetization, assets) |
| GitHub Actions workflows | 2 (тесты + синхронизация 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.md | YAML-схема 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-минутную консультацию.


