docs+chore: retry upload on TLS/504 + refresh README/RELEASING
Upload script: - Retry curl on transient network failures (504, schannel TLS abrupt close): up to 4 retries with 15s/45s/2m/5m backoff. Before each retry, list the release assets server-side — Gitea sometimes commits the body but times out the response, so the file may already be there at the expected size (skip retry). If present at wrong size (partial), delete before re-uploading. ASCII-only (PS5.1 reads files in CP1251 without BOM). Docs: - README: bump release/test badges to v0.5.1 / 51 tests; mention silent retry in the auto-update feature line. - RELEASING: rewrite around the new update-channel architecture, bridge tags, and dropped Gitea Actions workflows. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
14
README.md
14
README.md
@@ -2,8 +2,8 @@
|
||||
|
||||
Windows desktop приложение, которое напоминает делать упражнения во время работы за компьютером. Опционально подключается к Dota 2 и после каждого матча превращает статистику (смерти, убийства, ассисты) в количество повторений.
|
||||
|
||||
[](https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/latest)
|
||||
[]()
|
||||
[](https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/latest)
|
||||
[]()
|
||||
[]()
|
||||
|
||||
## Что внутри
|
||||
@@ -15,7 +15,7 @@ Windows desktop приложение, которое напоминает дел
|
||||
- **Игровая интеграция (Dota 2)** — Game State Integration читает статистику матча, после Победа/Поражение показывает экран с «причитающимися» повторениями (например `10 смертей × 3 = 30 приседаний`).
|
||||
- **Apple-style интерфейс** — Plus Jakarta Sans + Bricolage Grotesque, iOS-палитра, vibrancy sidebar, spring-анимации, светлая/тёмная/системная тема.
|
||||
- **Два языка** — русский и английский, переключение мгновенное.
|
||||
- **Auto-update** — приложение само скачивает новые версии из Gitea release (проверка каждый час).
|
||||
- **Auto-update** — приложение само скачивает новые версии из фиксированного `update-channel` (проверка каждый час, силент-ретрай при сетевых сбоях).
|
||||
|
||||
## Скриншоты
|
||||
|
||||
@@ -67,14 +67,16 @@ npm run release -- -Bump patch # bump версии + tag + push + upload в G
|
||||
|
||||
```
|
||||
src/shared/types.test.ts (4)
|
||||
src/shared/quiet-hours.test.ts (5)
|
||||
src/renderer/src/lib/format.test.ts (8)
|
||||
src/renderer/src/lib/history.test.ts (13)
|
||||
src/main/games/vdf.test.ts (11)
|
||||
src/renderer/src/i18n/i18n.test.ts (10)
|
||||
─────────────────────────────────────
|
||||
33 ✓
|
||||
─────────────────────────────────────────
|
||||
51 ✓
|
||||
```
|
||||
|
||||
Покрытие: чистые helpers (форматирование, парсер VDF для Steam-конфигов), i18n с плюрализацией для RU/EN, дефолты shared-типов.
|
||||
Покрытие: чистые helpers (форматирование, история/стрики, тихие часы, парсер VDF для Steam-конфигов), i18n с плюрализацией для RU/EN, дефолты shared-типов.
|
||||
|
||||
## Лицензия
|
||||
|
||||
|
||||
208
RELEASING.md
208
RELEASING.md
@@ -1,146 +1,142 @@
|
||||
# Релиз и автообновления
|
||||
|
||||
Документ описывает три способа выпустить новую версию. Все опираются на
|
||||
один и тот же артефакт — NSIS-инсталлятор `Exercise-Reminder-Setup-X.Y.Z.exe`,
|
||||
который сам решает: устанавливать заново или обновлять существующую копию.
|
||||
Документ описывает, как выпускать новые версии и как устроена система
|
||||
авто-обновлений.
|
||||
|
||||
## TL;DR
|
||||
|
||||
```pwsh
|
||||
$env:GITEA_TOKEN = '<token из Gitea Settings → Applications>'
|
||||
npm run release -- -Bump patch # 0.2.0 → 0.2.1
|
||||
# или
|
||||
npm run release -- -Version 0.3.0
|
||||
npm run release -- -Bump patch # 0.5.1 → 0.5.2
|
||||
npm run release -- -Bump minor -BridgeTags v0.5.0 # 0.5.x → 0.6.0 + bridge
|
||||
npm run release -- -Version 1.0.0
|
||||
```
|
||||
|
||||
Скрипт сделает всё сам: бамп версии, коммит, тег, push, тесты, сборка
|
||||
инсталлятора, создание Gitea release с заметками из коммитов, загрузка
|
||||
артефактов.
|
||||
Скрипт делает всё сам: бамп версии, коммит, тег, push, тесты, сборка
|
||||
инсталлятора, загрузка в Gitea releases.
|
||||
|
||||
После публикации релиза установленные у пользователей копии в течение
|
||||
~6 часов проверят `latest.yml` на Gitea и предложат обновление через UI.
|
||||
## Архитектура auto-update
|
||||
|
||||
---
|
||||
### Где лежат артефакты
|
||||
|
||||
## Как работает auto-update
|
||||
Каждый выпуск публикует три файла:
|
||||
|
||||
1. На каждом релизе вместе с `.exe` публикуется `latest.yml` —
|
||||
манифест с версией, размером, sha512 хешем.
|
||||
2. Приложение (через `electron-updater`) каждые 6 часов делает HTTP
|
||||
GET на `<gitea>/AnRil/laude/releases/download/v<current>/latest.yml`.
|
||||
3. Если версия в манифесте выше текущей — статус становится
|
||||
`available`, в Settings → Обновления появляется кнопка «Скачать».
|
||||
4. После скачивания — статус `downloaded`, кнопка «Перезапустить».
|
||||
5. При перезапуске NSIS установщик из дельты или полный накатывается
|
||||
поверх существующей инсталляции. Данные в `%APPDATA%\Exercise Reminder\`
|
||||
сохраняются.
|
||||
```
|
||||
Exercise-Reminder-Setup-X.Y.Z.exe # NSIS-инсталлятор (~80 MB)
|
||||
Exercise-Reminder-Setup-X.Y.Z.exe.blockmap # для differential update (~90 KB)
|
||||
latest.yml # манифест: версия + хеш + размер
|
||||
```
|
||||
|
||||
**Важно:** репозиторий `laude` приватный. Чтобы auto-update работал на
|
||||
машинах конечных пользователей, либо:
|
||||
- сделать репозиторий публичным, либо
|
||||
- сделать публичными только релизы (Gitea: Release Settings),
|
||||
- либо подписывать запросы токеном (нужен код в `updater.ts`,
|
||||
использующий `autoUpdater.requestHeaders`).
|
||||
И они одновременно публикуются в **три-четыре места** на Gitea:
|
||||
|
||||
## Способ 1 — скрипт релиза (рекомендованный сейчас)
|
||||
| Release tag | Назначение |
|
||||
|------------------|-------------------------------------------------------------|
|
||||
| `vX.Y.Z` | Архив + changelog для людей |
|
||||
| `update-channel` | **Фиксированный URL для auto-updater** (никогда не меняется) |
|
||||
| `vN.M.K` (bridge)| Мост: чтобы клиенты на старых версиях нашли обновление |
|
||||
|
||||
Самый прямой путь, не зависит от Gitea Actions runners.
|
||||
### Что приложение запекает в бинарник
|
||||
|
||||
В `package.json` → `build.publish.url`:
|
||||
|
||||
```
|
||||
https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/download/update-channel
|
||||
```
|
||||
|
||||
Этот URL **никогда не меняется**. Все версии (и сегодняшние, и будущие)
|
||||
проверяют один и тот же `update-channel/latest.yml`.
|
||||
|
||||
### Цикл проверки
|
||||
|
||||
1. При запуске и каждый час `electron-updater` делает GET на
|
||||
`…/update-channel/latest.yml`.
|
||||
2. Если в манифесте версия выше текущей — Settings → Обновления показывает
|
||||
«Доступно vX.Y.Z». По клику качается `.exe` (или differential по
|
||||
`.blockmap`).
|
||||
3. После скачивания — кнопка «Перезапустить». NSIS обновляет инсталляцию
|
||||
поверх с сохранением `%APPDATA%\Exercise Reminder\app-state.json`.
|
||||
|
||||
### Bridge-теги (миграционный период)
|
||||
|
||||
До v0.5.1 publish.url был `…/releases/download/v${version}` — у каждой
|
||||
версии свой адрес. Установленные ранее копии запекли старый URL.
|
||||
Чтобы они нашли обновление, новые артефакты также заливаются в их
|
||||
старые releases (флаг `-BridgeTags`).
|
||||
|
||||
После того как все клиенты получили v0.5.1 или выше, аргумент
|
||||
`-BridgeTags` можно перестать использовать — все будущие версии берут
|
||||
обновления через `update-channel`.
|
||||
|
||||
### Поведение при ошибках
|
||||
|
||||
- Hourly auto-check работает в **silent**-режиме: сетевые ошибки
|
||||
логируются в консоль, но **не** показываются как красный баннер.
|
||||
Следующая попытка через час.
|
||||
- Boot-check ретраит 3 раза с backoff 30s/2m/5m перед тем как сдаться.
|
||||
- Только ручной клик «Проверить обновления» показывает ошибку, если
|
||||
она есть.
|
||||
|
||||
## Команды
|
||||
|
||||
```pwsh
|
||||
# Один раз — получить токен в Gitea (Settings → Applications)
|
||||
# и сохранить в переменную окружения. Право — write:repository.
|
||||
# Один раз — токен из Gitea Settings -> Applications (write:repository).
|
||||
[Environment]::SetEnvironmentVariable('GITEA_TOKEN', '<token>', 'User')
|
||||
|
||||
# Релиз
|
||||
npm run release -- -Bump patch # patch (0.2.0 → 0.2.1)
|
||||
npm run release -- -Bump minor # minor (0.2.0 → 0.3.0)
|
||||
npm run release -- -Bump major # major (0.2.0 → 1.0.0)
|
||||
npm run release -- -Bump patch # patch (0.5.1 -> 0.5.2)
|
||||
npm run release -- -Bump minor # minor (0.5.x -> 0.6.0)
|
||||
npm run release -- -Bump major # major
|
||||
npm run release -- -Version 1.2.3 # точная версия
|
||||
npm run release -- -DryRun # посмотреть план без действий
|
||||
npm run release -- -BridgeTags v0.4.0,v0.5.0 # дополнительные мосты
|
||||
npm run release -- -DryRun # план без действий
|
||||
```
|
||||
|
||||
Что делает скрипт:
|
||||
1. Проверяет что нет незакоммиченных изменений
|
||||
2. Бампит версию в `package.json`, коммитит
|
||||
3. Прогоняет `npm run typecheck` и `npm run test:run`
|
||||
4. Собирает `npm run dist` (NSIS + блокмап + latest.yml)
|
||||
5. Создаёт тег `vX.Y.Z`, пушит main и тег в origin
|
||||
6. Через Gitea API создаёт release с заметками из git log
|
||||
7. Загружает три файла как assets: `.exe`, `.exe.blockmap`, `latest.yml`
|
||||
Что делает `release.ps1`:
|
||||
|
||||
## Способ 2 — Gitea Actions (если есть runners)
|
||||
|
||||
Workflows лежат в `.gitea/workflows/`:
|
||||
|
||||
- **`ci.yml`** — на push в main и на PR. Запускает typecheck +
|
||||
unit-тесты + smoke-сборку (без NSIS). Кладёт распакованную сборку
|
||||
как artifact на 7 дней.
|
||||
- **`release.yml`** — на push тега `v*.*.*`. Сверяет тег с версией
|
||||
в `package.json`, прогоняет тесты, собирает NSIS-инсталлятор,
|
||||
создаёт Gitea release с заметками, загружает артефакты.
|
||||
|
||||
Чтобы release workflow работал — в репозитории нужен secret
|
||||
`GITEA_TOKEN` (Gitea Repo Settings → Secrets). Этот же токен может быть
|
||||
переиспользован из `Способа 1`.
|
||||
|
||||
Для запуска release workflow:
|
||||
```bash
|
||||
git tag v0.3.0
|
||||
git push origin v0.3.0
|
||||
```
|
||||
|
||||
## Способ 3 — руками
|
||||
|
||||
Если что-то сломалось в автоматизации:
|
||||
|
||||
```pwsh
|
||||
npm run typecheck
|
||||
npm run test:run
|
||||
npm run dist
|
||||
# В release/ появятся:
|
||||
# Exercise-Reminder-Setup-X.Y.Z.exe
|
||||
# Exercise-Reminder-Setup-X.Y.Z.exe.blockmap
|
||||
# latest.yml
|
||||
```
|
||||
|
||||
Затем в Gitea UI: Releases → Draft new release → загрузить три файла.
|
||||
1. Проверяет чистоту дерева.
|
||||
2. Бампит `package.json`, коммитит как `chore(release): vX.Y.Z`.
|
||||
3. `npm run typecheck` + `npm run test:run`.
|
||||
4. `npm run dist` → NSIS-инсталлятор + blockmap + latest.yml в `release/`.
|
||||
5. `git tag vX.Y.Z` и push main + tag в origin.
|
||||
6. Через `upload-release-assets.ps1` заливает артефакты в каждый тег
|
||||
из списка: `vX.Y.Z`, `update-channel`, и все `-BridgeTags`.
|
||||
7. Каждая заливка ретраит до 4 раз с backoff 15s/45s/2m/5m на 504.
|
||||
|
||||
## Тестирование auto-update
|
||||
|
||||
Удобный способ проверить, что цикл работает:
|
||||
1. Установить какую-нибудь старую версию через `.exe` из её release.
|
||||
2. Релизнуть свежую версию.
|
||||
3. В установленной копии: Settings → Обновления → Проверить.
|
||||
4. Должно показать «Доступна vX.Y.Z» с кнопкой «Скачать».
|
||||
5. Скачать → Перезапустить → проверить версию.
|
||||
|
||||
1. Релизнуть `0.x.0` через `npm run release`.
|
||||
2. Установить полученный `.exe` на машину.
|
||||
3. Релизнуть `0.x.1` (любой бамп).
|
||||
4. На установленной копии открыть Settings → Обновления → Проверить.
|
||||
Должно показать «Доступно обновление v0.x.1».
|
||||
5. Скачать → Перезапустить → проверить версию в окне «О программе»
|
||||
(или в Settings).
|
||||
|
||||
Для dev-режима (`npm run dev`) auto-updater отключён — статус сразу
|
||||
становится `unsupported` с пояснением.
|
||||
Для `npm run dev` auto-updater отключён — статус сразу `unsupported`.
|
||||
|
||||
## Откат релиза
|
||||
|
||||
Если опубликовали плохой релиз:
|
||||
|
||||
1. Удалить release в Gitea UI (или через API).
|
||||
2. Удалить тег: `git push origin :refs/tags/vX.Y.Z` и локально
|
||||
`git tag -d vX.Y.Z`.
|
||||
3. Откатить bump-коммит: `git revert <hash>` или `git reset --hard HEAD~1`
|
||||
(если ещё не пушили дальше).
|
||||
4. Релизнуть тот же номер заново — auto-updater на клиентах увидит
|
||||
тот же манифест и не предложит обновление (если sha512 совпадёт).
|
||||
Если содержание поменялось — увидит и предложит обновиться. На
|
||||
практике лучше выпустить hotfix-патч `X.Y.Z+1`, чем переписывать
|
||||
существующий релиз.
|
||||
2. `git push origin :refs/tags/vX.Y.Z` и `git tag -d vX.Y.Z`.
|
||||
3. `git revert <bump-hash>` (бамп уже запушен).
|
||||
4. Если артефакты успели уехать в `update-channel` — перезалить туда
|
||||
предыдущую версию: `pwsh scripts/upload-release-assets.ps1 -Tag update-channel -AssetVersion <previous>`.
|
||||
|
||||
На практике лучше выпустить hotfix-патч `X.Y.Z+1`, чем откатывать.
|
||||
|
||||
## Gitea Actions
|
||||
|
||||
Раньше в `.gitea/workflows/` лежали `ci.yml` и `release.yml`. Они
|
||||
требуют Gitea Actions runners (отдельная служба, у нас не настроена),
|
||||
поэтому каждая push-операция оставляла зависший workflow run в Actions
|
||||
tab. Workflows удалены, has_actions на репозитории выключен,
|
||||
Actions tab возвращает 404. Если когда-нибудь захочется CI — добавить
|
||||
обратно `.gitea/workflows/*.yml` + поднять runners.
|
||||
|
||||
## Что попадает в установщик
|
||||
|
||||
См. `build` секцию `package.json`:
|
||||
См. `build.files` в `package.json`:
|
||||
|
||||
- `out/**/*` — собранный код (main + preload + renderer)
|
||||
- `resources/**/*` — иконки
|
||||
|
||||
Никаких node_modules, исходников, тестов, README — `electron-builder`
|
||||
сам распаковывает и упаковывает только необходимое.
|
||||
Без `node_modules`, без исходников, без тестов — `electron-builder`
|
||||
сам выбирает только необходимое.
|
||||
|
||||
@@ -48,7 +48,7 @@ if (-not $Tag) {
|
||||
$Tag = "v$pkgVersion"
|
||||
}
|
||||
if (-not $AssetVersion) {
|
||||
# Derive from tag when possible (vX.Y.Z → X.Y.Z); otherwise read package.json.
|
||||
# Derive from tag when possible (vX.Y.Z -> X.Y.Z); otherwise read package.json.
|
||||
if ($Tag -match '^v\d+\.\d+\.\d+') {
|
||||
$AssetVersion = $Tag.TrimStart('v')
|
||||
} else {
|
||||
@@ -101,7 +101,7 @@ try {
|
||||
if ($prev) {
|
||||
$log = (& git log --pretty=format:"- %s" "$prev..$Tag") -join "`n"
|
||||
} else {
|
||||
# No prior tag — list last 10 commits up to this tag.
|
||||
# No prior tag - list last 10 commits up to this tag.
|
||||
$log = (& git log --pretty=format:"- %s" -n 10 "$Tag") -join "`n"
|
||||
}
|
||||
$body = "### Changes`n`n$log`n`n---`n`nInstaller below: run it; if app is already installed, it updates in place and keeps your settings."
|
||||
@@ -181,7 +181,7 @@ foreach ($asset in @($installer, $blockmap, $manifest)) {
|
||||
-Method Get -Headers $headers
|
||||
$existing = $check | Where-Object { $_.name -eq $name }
|
||||
if ($existing -and $existing.size -eq $size) {
|
||||
Write-Host " Asset already present server-side ($($existing.size) bytes) — skipping retry." -ForegroundColor DarkGray
|
||||
Write-Host " Asset already present server-side ($($existing.size) bytes) - skipping retry." -ForegroundColor DarkGray
|
||||
$uploaded = $true
|
||||
break
|
||||
}
|
||||
@@ -210,7 +210,7 @@ foreach ($asset in @($installer, $blockmap, $manifest)) {
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
$uploaded = $true
|
||||
} else {
|
||||
Write-Host " curl exit $LASTEXITCODE — will retry." -ForegroundColor Yellow
|
||||
Write-Host " curl exit $LASTEXITCODE - will retry." -ForegroundColor Yellow
|
||||
$attempt++
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user