feat: auto-update, тесты и CI/CD
Полная автоматизация релизного цикла. == Auto-update (electron-updater) == - src/main/updater.ts — обёртка над autoUpdater с дискриминированным UpdaterStatus union и broadcast через IPC. autoDownload=false, пользователь сам жмёт «Скачать». allowDowngrade=false. Проверка каждые 6 часов, первая через 5с после старта. - В dev-режиме (app.isPackaged=false) статус сразу становится 'unsupported' с пояснением — никаких exceptions из updater'а. - build.publish в package.json: provider=generic, url указывает на Gitea release assets конкретной версии. - src/main/ipc.ts: 4 новых канала — status/check/download/install. - src/preload: API window.api.updater* + onUpdaterStatus. - src/renderer/src/components/UpdaterCard.tsx: HUD-карточка в Settings с состояниями idle/checking/available/downloading/downloaded/error, прогресс-бар с скоростью в МБ/с. == Тесты (vitest) == - vitest.config.ts с алиасами @shared / @renderer - 23 теста, все зелёные: * format.test.ts — formatCountdown, formatInterval (8 cases) * vdf.test.ts — parseVdf / stringifyVdf / round-trip (11 cases) * types.test.ts — DEFAULT_SETTINGS, SAMPLE_EXERCISES sanity (4) - npm scripts: test (watch), test:run (CI) == CI/CD (Gitea Actions) == - .gitea/workflows/ci.yml — на push/PR: typecheck + тесты + smoke-сборка - .gitea/workflows/release.yml — на тег v*.*.*: сборка NSIS + Gitea release == Локальный релизный скрипт == - scripts/release.ps1 — один скрипт от бампа версии до публикации через Gitea API (params: -Bump patch/minor/major, -Version, -DryRun) - npm run release — обёртка - RELEASING.md — полная инструкция Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,8 @@ import type {
|
||||
GameStatus,
|
||||
MatchSummary,
|
||||
Settings,
|
||||
Tick
|
||||
Tick,
|
||||
UpdaterStatus
|
||||
} from '@shared/types'
|
||||
|
||||
type Unsub = () => void
|
||||
@@ -78,13 +79,23 @@ const api = {
|
||||
simulateMatchEnd: (id: GameId, stats: Record<string, number>): Promise<void> =>
|
||||
ipcRenderer.invoke('dev:simulateMatchEnd', id, stats),
|
||||
|
||||
// Auto-updater
|
||||
updaterStatus: (): Promise<UpdaterStatus> =>
|
||||
ipcRenderer.invoke(IPC.updaterStatus),
|
||||
updaterCheck: (): Promise<UpdaterStatus> =>
|
||||
ipcRenderer.invoke(IPC.updaterCheck),
|
||||
updaterDownload: (): Promise<void> => ipcRenderer.invoke(IPC.updaterDownload),
|
||||
updaterInstall: (): Promise<void> => ipcRenderer.invoke(IPC.updaterInstall),
|
||||
|
||||
onTick: (h: Handler<Tick[]>): Unsub => on(IPC.evtTick, h),
|
||||
onFire: (h: Handler<Exercise>): Unsub => on(IPC.evtFire, h),
|
||||
onMatchEnd: (h: Handler<MatchSummary>): Unsub => on(IPC.evtMatchEnd, h),
|
||||
onStateChanged: (h: Handler<AppState>): Unsub => on(IPC.evtStateChanged, h),
|
||||
onThemeChanged: (h: Handler<'light' | 'dark'>): Unsub => on(IPC.evtThemeChanged, h),
|
||||
onAccentChanged: (h: Handler<string>): Unsub => on(IPC.evtAccentChanged, h),
|
||||
onGamesChanged: (h: Handler<GameStatus[]>): Unsub => on(IPC.evtGamesChanged, h)
|
||||
onGamesChanged: (h: Handler<GameStatus[]>): Unsub => on(IPC.evtGamesChanged, h),
|
||||
onUpdaterStatus: (h: Handler<UpdaterStatus>): Unsub =>
|
||||
on(IPC.evtUpdaterStatus, h)
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('api', api)
|
||||
|
||||
Reference in New Issue
Block a user