Open SleepySquash opened 8 months ago
upgrader
по сути работает на desktop платформах следующим образом: кушает .xml
файлик со списком версий, как-то сравнивает текущую версию и предлагает на основании этого всплывающее окно с изменениями. На Android'е и iOS'е он умеет перекидывать в google play и app store.
В теории этого всё можно достичь руками: создать UpgradeWorker
сервис, который будет ходить на ту же gh-pages
ветку или куда-либо ещё, парсить версии, сравнивать с текущей и затем:
1) Для начала можно просто делать launchUrl
с ссылкой на новое приложение или в google play / app store.
2) Затем для десктопов нужно будет написать да даже на Dart'е CLI утилиту, которая будет сама качать и распаковывать приложение, а затем открывать его.
Сам файл со списком версий и их изменениями можно генерить в CI да даже при каждом коммите - брать название коммита, пихать его как изменения, итд.
Мы можем использовать GitHub Releases для определения релизов, сравнения с текущей версией и предложения пользователю обновиться - и ссылки, и список релизов, всё есть. Не нужно изобретать какие-либо XML форматы, файлы хостить, итд.
Проблемы:
1) Могут существовать разные версии приложения, которые двигаются по иным, отличным от релизов в репозитории, траекториям. Решение: хостить отдельно JSON файл в формате, который возвращает GitHub Releases API. Хостить этот файл можно даже в составе Docker контейнера, который пушится из CI, а клиенты, скачиваемые с этого контейнера, будут ходить к нему за информацией о свежих релизах.
2) Языки: список изменений по-хорошему должен быть представлен на разных языках. Пока мысли такие: т.к. notes - это Markdown, то мы можем размещать спойлеры со списком изменений на языках, названия спойлеров которых будут соответствовать ftl формату: ru-RU
, en-US
, итд. Но неясно, а каким образом эти изменения в релизы прописывать? Руками, получается, после CI редактировать список изменений? Неприятный момент, хотелось бы автоматизировать. Как вариант, у prepare PR'ов менять где-то какие-то списки изменений, которые CI будет кушать и по ним оформлять релизы: например, какой-нибудь RELEASES.md
?
Формат JSON'а, который нужно будет хостить, достаточно прост:
[
{
"name": "0.1.0-alpha.12.3",
"published_at": "2024-02-23T15:03:08Z",
"body": "СПИСОК_ИЗМЕНЕНИЙ",
"assets": [
{
"name": "docs.zip",
"content_type": "application/zip",
"size": 41149389,
"browser_download_url": "ССЫЛКА_НА_СКАЧИВАНИЕ"
}
]
}
]
Альтернатива формату GitHub API для релизов - это Sparkle Appcast, который ещё и open source. Это либа для обновлялок исключительно для macOS, но его XML формат файла плюс минус распространился и используется независимо от самой либы (например, тот же upgrader
). Формат примерно такой:
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>Tittle</title>
<link>http://you.com/app/appcast.xml</link>
<description>Most recent changes with links to updates.</description>
<language>en</language>
<item>
<title>Version 2.0 (2 bugs fixed; 3 new features)</title>
<sparkle:releaseNotesLink>http://you.com/app/2.0.html</sparkle:releaseNotesLink>
<pubDate>Wed, 09 Jan 2006 19:20:11 +0000</pubDate>
<enclosure url="ССЫЛКА_НА_СКАЧИВАНИЕ" />
</item>
</channel>
</rss>
@tyranron, discussed the mechanism of upgrading: we implement an UpgradeWorker
service that fetches the release notes from some URL (currently GitHub Releases API endpoint in #907) and prompts the user to update. For now update must be performed manually, afterwards we will implement some CLI binary that will do it automatically. We may host release information files in Docker containers that are pushed here on GitHub - clients downloaded from such containers fetch those files, this allows us to have different release tracks (e.g. main
branch will follow its own, which updates on every push, release
branch - only its own, etc). The files hosted in containers should be generated on CI: for release
it should contain release notes, for main
pushes it may take the commit message, for example.
Yet the two questions arise:
1) Which format should be used for the file hosted? XML in Sparkle format, GitHub Release API JSON format, or implement our own?
2) The first question should also meet the following requirement: release notes should be in a human-readable format with multiple languages supported (for release
s at least, or possible for every release track out there).
With Sparkle format the language is available out of the box: it is an RSS tag. With GitHub API only a workaround comes to my head: use <spoiler>
s in Markdown for notes in different languages.
Regarding how the release notes should be provided to the CI at all: I suppose we may put a file with notes right here in the repository, that we update with PRs or commits. File is manually written on different languages. CI takes that file and puts the notes to the XML/JSON file that will be hosted in the container. Still thinking about this, not sure.
Discussed:
1) Use XML in Sparkle format (it's more popular, thus more supported).
2) When preparing release, put the notes in separate languages to the files: release_notes/ru-RU.md
, release_notes/en-US.md
.
3) CI uses the notes when tag is pushed in its jobs (appcast
and appcast-edge
, e.g.): firstly fetches the existing appcast.xml
from gh-pages
, then appends the release details on both languages, and uploads it as an artefact. The artefact is then used in Docker container, and after the GitHub release job, the appcast.xml
formed is pushed to the gh-pages
, so the release history isn't lost.
4) CI on the pushes to main (appcast-edge
job) forms the single release appcast.xml
file with the git describe
version and link_to_changelog
as its description. This file is only to be put into Docker edge container.
5) Applications built fetch the file from the host they were downloaded from.
How the release cycle should work: appcast.xml consists of <item>
s that represent releases. Thus CI should build such files, push them to gh-pages
and reconstruct a single appcast.xml from those items on every release. That way reconstruction of the whole XML is possible at any moment, in case appcast.xml gets corrupted.
On CI that should happen before Docker container is built, however appcast.xml file should be persisted to gh-pages only after the GitHub release succeeds.
@SleepySquash Because this feature is still WIP not sure it is a bug, but looks like auto updater doesn't check application version when showing notification:
@50U10FCA7, thank you for the notice. The version displayed in about
section isn't the real version of the application. So that's indeed a bug, which is strange, considering the 0.1.0-alpha.13 has been just released and it still prompts the 0.1.0-alpha.12.3. I'll investigate this.
Regarding updates, that user MUST update to and which they can't skip (e.g. when API breaking changes are introduced on backend, so that older versions won't function at all), there're two approaches we might follow:
1) Treat every major/minor release and every major alpha
/beta
bump as such version. This may go wrong really fast: imagine we decide to release 1.0.0
instead of 0.13.0
with NO changes. And by doing so we'll force every user to update to basically no breaking changes.
2) Use the sparke:criticalUpdate
tag documented in Sparkle Appcast XML file. This tag is added to a release, that just be treated as a critical one, meaning every single release lower must update to this one. That's exactly what we want. Except the question is: how will CI know, whether this bump up is a critical one?
a) Add a FCM tag to prepare ... project release
. E.g.:
Prepare 0.1.0-alpha.14 project release (#...) [critical]
b) ...?
UPD. Discussed: let's follow the first approach, meaning treating every major/minor release as a critical update. That way we need to implement a tool for parsing the semvers and determining whether those are critical versions (plus cover that tool with unit tests).
Examples include:
@SleepySquash
@tyranron, oh, I see, we should treat every major release as a critical one (user can't skip those), makes sense.
~But should we treat every -alpha
and -beta
of 0.1.0
version as such? E.g. alpha.1
(critical) -> alpha.2
(critical) -> alpha.2.1
(critical)? Or perhaps it'd be reasonable to just use 0.1.0
, starting with the closest release?~
~I'd go with:~
0.1.0-alpha.1
-> 0.1.0-alpha.2
(critical)~0.1.0-alpha.2
-> 0.1.0-alpha.2.1
(non-critical)~0.1.0-alpha.10
-> 0.1.0-beta.1
(critical)~1.0.0
-> 2.0.0-alpha.1
(shouldn't even be prompted? Perhaps we'll introduce alpha
/beta
channels in the future)~~If you don't mind?~
UPD. Discussed: we'd go with 0.1.0
starting with the closest release. Thus the following examples regarding marking releases as critical or not make sense:
Background
Когда выходит новая версия приложения, нужно идти руками её качать.
Problem to solve
Узнать о наличии новой версии сейчас невозможно. Да и сам факт, что нужно руками всё перекачивать, удручает.
Possible solutions
Изобрести upgrader, который будет: 1) Проверять наличие новой версии приложения. 2) Автоматически скачивать новую версию и обновлять файлы.
Вероятно, можно написать даже на Dart'е какую-нибудь программку, которая будет качать и/или обновлять основное приложение, затем запустив его.
Roadmap:
main
branch. (#907)appcast.xml
to notify about updates on releases. (#929)UpgradeWorker
to detect critical releases that user must update to. (#973)MyProfile
page). (#1079)appcast.xml
in runtime.