diff --git a/README.md b/README.md index 9f13b62..5b60f58 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ Windows desktop приложение, которое напоминает делать упражнения во время работы за компьютером. Опционально подключается к Dota 2 и после каждого матча превращает статистику (смерти, убийства, ассисты) в количество повторений. -[![release](https://img.shields.io/badge/release-v0.4.0-orange)](https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/latest) -[![tests](https://img.shields.io/badge/tests-33%20passing-green)]() +[![release](https://img.shields.io/badge/release-v0.5.1-orange)](https://xn--90adajar8af4h.xn--p1ai/git/AnRil/laude/releases/latest) +[![tests](https://img.shields.io/badge/tests-51%20passing-green)]() [![platform](https://img.shields.io/badge/platform-Windows%2010%2F11-blue)]() ## Что внутри @@ -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` (проверка каждый час, силент-ретрай при сетевых сбоях). ## Скриншоты @@ -66,15 +66,17 @@ npm run release -- -Bump patch # bump версии + tag + push + upload в G ## Тесты ``` -src/shared/types.test.ts (4) -src/renderer/src/lib/format.test.ts (8) -src/main/games/vdf.test.ts (11) -src/renderer/src/i18n/i18n.test.ts (10) -───────────────────────────────────── - 33 ✓ +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) +───────────────────────────────────────── + 51 ✓ ``` -Покрытие: чистые helpers (форматирование, парсер VDF для Steam-конфигов), i18n с плюрализацией для RU/EN, дефолты shared-типов. +Покрытие: чистые helpers (форматирование, история/стрики, тихие часы, парсер VDF для Steam-конфигов), i18n с плюрализацией для RU/EN, дефолты shared-типов. ## Лицензия diff --git a/RELEASING.md b/RELEASING.md index 47574b7..8d5d15f 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -1,146 +1,142 @@ # Релиз и автообновления -Документ описывает три способа выпустить новую версию. Все опираются на -один и тот же артефакт — NSIS-инсталлятор `Exercise-Reminder-Setup-X.Y.Z.exe`, -который сам решает: устанавливать заново или обновлять существующую копию. +Документ описывает, как выпускать новые версии и как устроена система +авто-обновлений. ## TL;DR ```pwsh $env:GITEA_TOKEN = '' -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 на `/AnRil/laude/releases/download/v/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', '', '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 -- -Version 1.2.3 # точная версия -npm run release -- -DryRun # посмотреть план без действий +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 -- -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 ` или `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 ` (бамп уже запушен). +4. Если артефакты успели уехать в `update-channel` — перезалить туда + предыдущую версию: `pwsh scripts/upload-release-assets.ps1 -Tag update-channel -AssetVersion `. + +На практике лучше выпустить 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` +сам выбирает только необходимое. diff --git a/scripts/upload-release-assets.ps1 b/scripts/upload-release-assets.ps1 index dc72a26..f87ec83 100644 --- a/scripts/upload-release-assets.ps1 +++ b/scripts/upload-release-assets.ps1 @@ -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++ } }