Я давно люблю и слежу за миром Open Source. Но у Гитхаба есть огромная проблема: интересные, самобытные проекты часто теряются и не получают заслуженного хайпа, если у их создателя нет прокачанного Твиттера или связей. В то же время на главную страницу трендов регулярно выходят гиганты, за которыми стоят корпорации.Вторая боль — входной барьер для обычных людей. Обычный дизайнер, маркетолог или копирайтер, придя на GitHub за бесплатной программой, впадает в ступор. Где тут кнопка «Скачать»? Что такое ветки, коммиты и релизы? Куда нажимать, чтобы оно просто запустилось?
VibeMarket — витрина бесплатных open‑source приложений в духе App Store. В ней подборка бесплатных программ, рейтинг по звёздам GitHub и кнопка «Скачать», которая отдаёт установщик под вашу систему. Есть песочница, куда любой разработчик может подать своё приложение. Своего сервера, базы данных и модераторов у проекта нет.
Всю работу бэкенда берёт на себя GitHub: Pages раздаёт сайт, Issues служат формой заявок, реакции 👍 под заявкой работают как голосование, а робот в Actions раз в час пересобирает каталог. По коду это выглядит так: один index.html примерно на 3400 строк, робот на 570 строк, модуль разбора релизов на 100 строк и необязательный сервер для входа через GitHub на 400 строк. Что получилось в итоге: четыре языка интерфейса — русский, английский, испанский и китайский; страница сама понимает, нужен вам.dmg,.msi или.AppImage, и для Apple Silicon или Intel; есть песочница со сторонними приложениями, где заявку подаёт сам автор, сообщество голосует, а робот публикует; плюс жалобы, автоматическое скрытие и аварийный стоп‑кран.
Витрина: один HTML‑файл без сборки
Фронтенд — это один index.html: вёрстка, стили и скрипт в одном файле, без фреймворка и без сборщика. Pages раздаёт файл как есть, поэтому CI для фронтенда не нужен. Любой может открыть исходник страницы и убедиться, что трекеров нет. И нет цепочки поставок, которую можно скомпрометировать: никаких node_modules.Каталог встроен в страницу как снимок. При загрузке страница запрашивает catalog.json со свежими звёздами, песочницей и файлами для скачивания. Если файла нет, например при открытии с диска, всё продолжает работать на снимке. Тексты интерфейса на русском встроены в страницу. Остальные языки лежат в locales/*.json и подгружаются при переключении. Отдельный скрипт check-locales.mjs на каждом деплое сверяет, что во всех языках одинаковые ключи и плейсхолдеры. Иначе забытая строка всплыла бы только у пользователя.
База данных — это JSON‑файл, а пишет в него робот
Робот — это scripts/robot.mjs, обычный скрипт на Node 24 без зависимостей. GitHub Actions запускает его раз в час по расписанию, на каждое событие в Issues (новая заявка, правка, метка) и вручную. За проход робот проверяет заявки, считает голоса, публикует и снимает приложения, обновляет звёзды и файлы для скачивания. Результат он записывает в catalog.json и коммитит, только если что‑то изменилось. Получилась забавная деталь: git log catalog.json — это полный журнал изменений «базы данных» с датами и диффами. Откатить неудачное изменение можно обычным git revert. Проход робота занимает несколько секунд, вся пробежка вместе с публикацией сайта — меньше минуты.
Подводные камени коммит робота не запускает деплой
Первая версия была логичной: робот коммитит catalog.json, push в main запускает workflow публикации, сайт обновляется. Сайт не обновлялся. Оказалось, что события от GITHUB_TOKEN не запускают новые workflow: GitHub так защищается от бесконечных циклов. В публичных репозиториях GitHub отключает cron‑расписание после 60 дней без активности. Для «автономного» проекта это неприятно. Спасает то, что робот коммитит catalog.json, когда меняются звёзды, а у 49 популярных проектов они меняются каждый час. Пока каталог живой, активность в репозитории есть всегда. На сайте есть обычная форма «Добавить приложение» с предпросмотром карточки. Кнопка «Отправить» ничего никуда не шлёт. Она проверяет поля и показывает ссылку на новую задачу на GitHub, где форма уже заполнена.
Где хранить состояние, если нет базы?
Роботу нужно помнить, когда началось голосование и в каком статусе заявка. Базы нет, а catalog.json для этого не годится: там только то, что видит сайт. Решение — HTML‑комментарий внутри комментария робота к заявке. Тут же обнаружилась дыра. Любой может оставить под заявкой комментарий с таким же маркером и датой начала голосования месячной давности. Тогда голосование закончится мгновенно. Поэтому робот доверяет только комментариям от github-actions[bot]: подделать автора комментария через API нельзя. Если голосов хватило, приложение попадает в песочницу с пометкой «Стороннее», а заявка закрывается. Через 90 дней приложение с 500 звёздами и без жалоб переезжает в обычную категорию на главной.
Самое сложное: кнопка «Скачать»
Сначала кнопка GET, как в App Store, только проигрывала анимацию «Installing…» и вела на страницу релизов. Для маркета, смысл которого в упрощении, это была главная фальшь, и её хотелось убрать. Браузер не умеет устанавливать программы, но может скачать установщик. Значит, надо понять, какой из файлов релиза подходит посетителю.
Правила пришлось отлаживать на реальных релизах проектов. Вот что всплыло:
-
darwinсодержитwin. Если искать Windows раньше macOS, файлlazygit_darwin_arm64.tar.gzуезжает в Windows. Порядок проверок важен. -
Самый свежий релиз — не последняя версия. У Godot самым новым по дате оказался служебный выпуск старой ветки 3.6, а не актуальная 4.7. Теперь первым берётся релиз с отметкой
isLatest, остальные — по дате. -
GraphQL отдаёт не больше 100 файлов. У VSCodium их 180, и сборки для Windows просто не попадали в выборку. Для таких релизов робот дочитывает файлы через REST с пагинацией.
-
Монорепозитории. У Mullvad последний релиз содержит только приложение для Android, а версии для компьютеров лежат в предыдущем. Поэтому релиз выбирается отдельно для каждой системы: самый свежий, где есть файлы для неё. Bitwarden выпускает из одного репозитория CLI, десктоп и веб, поэтому для него в
rules.jsonзадан фильтр тегов^desktop-v. -
Особые сборки. У Ollama есть сборки под ROCm и Jetson, у Godot — сборки Mono, у OBS — отладочные dSYM, у HandBrake — консольный
HandBrakeCLI.dmg. Узкие сборки показываются, только если обычной для этого процессора нет. Отладочные и консольные отбрасываются всегда. -
Не всё, что лежит в релизах, — приложение. У Tauri в релизах консольная утилита фреймворка, у ComfyUI — portable‑сборка только для видеокарт NVIDIA. Такие репозитории перечислены в
downloads.skip, и кнопка ведёт на сайт проекта.
Итог прохода: 5 GraphQL‑запросов по 1 очку лимита и дочитывание VSCodium через REST. Вместе со звёздами и заявками это 11 запросов к API на весь проход. В catalog.json попадают 288 файлов, это 131 КБ, или 21 КБ после gzip.
Выводы
Свои выводы можете сделать самостоятельно, но главный вывод для меня такой: для каталога, который меняется раз в час и где каждое изменение должно быть прозрачным, GitHub закрывает почти весь бэкенд — хранилище с историей, очередь задач, аутентификацию и права доступа, а также формы, голосование, хостинг и CDN.Свой код нужен только на склейку и на правила.
Если у вас есть бесплатное приложение с открытым кодом:
-
Добавьте репозиторию тему
vibemarket. -
Выпустите релиз.
-
Подайте заявку через форму на сайте.
Github проекта с кодом:https://github.com/NickGBar/VibeMarket
ссылка на оригинал статьи https://habr.com/ru/articles/1101156/