- README : fonctionnalités, installation, build, tests (302 + 20 E2E), couverture 94%, workflow mise à jour latest.yml, changelog v1.1.6 - CLAUDE.md : règles de collaboration, architecture, conventions - Plans de conception : DDD, electron-updater, accouplement, toast Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
12 KiB
Migration electron-updater + Gitea — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Remplacer le système de mise à jour custom (download HTTP + script batch) par electron-updater, en gardant Gitea comme source de releases.
Architecture: Approche hybride — appel API Gitea pour découvrir la dernière version (car Gitea n'a pas d'URL fixe "latest"), puis electron-updater (generic provider) pour le cycle download → vérification sha512 → installation NSIS → restart. Les IPC channels restent identiques pour que UpdateBanner.ts et preload.ts n'aient aucun changement.
Tech Stack: electron-updater 6.x, Electron 32.x, TypeScript
Task 1: Installer electron-updater et configurer package.json
Files:
- Modify:
package.json
Step 1: Installer la dépendance
npm install electron-updater
electron-updaterdoit être dansdependencies(pasdevDependencies) car il tourne dans l'app packagée.
Step 2: Ajouter la config publish dans la section build
Dans package.json, ajouter dans "build" :
"publish": {
"provider": "generic",
"url": "https://gitea.mickael-pol.fr/mickael/dd-timer/releases/download/latest"
}
Cette URL est un placeholder — au runtime, on la remplace dynamiquement via
setFeedURL(). Son seul rôle est de déclencher la génération delatest.ymlpar electron-builder.
Step 3: Commit
git add package.json package-lock.json
git commit -m "chore: add electron-updater dependency + publish config"
Task 2: Réécrire la section mise à jour dans main.ts
Files:
- Modify:
src/infrastructure/electron/main.ts:1-16(imports) - Modify:
src/infrastructure/electron/main.ts:37-48(UpdateInfo + state vars) - Delete:
src/infrastructure/electron/main.ts:254-450(tout le bloc update custom) - Add: nouveau bloc update avec electron-updater (~60 lignes)
Step 1: Ajouter l'import electron-updater, supprimer les imports inutiles
Remplacer les imports en haut du fichier :
import {
app,
BrowserWindow,
Tray,
Menu,
nativeImage,
ipcMain,
Notification,
dialog,
} from 'electron';
import path from 'path';
import fs from 'fs';
import { autoUpdater } from 'electron-updater';
Supprimer :
import https from 'https',import http from 'http',import os from 'os',import { spawn } from 'child_process'— ils ne sont plus nécessaires pour les mises à jour.Attention :
httpsethttpsont encore utilisés par le bloc ntfy (lignes 214-243). Vérifier si d'autres usages existent avant de supprimer. Si ntfy les utilise → garderhttpsethttp. Supprimer seulementosetspawnsi plus aucun usage.
Step 2: Simplifier les variables d'état
Remplacer l'interface UpdateInfo et les variables updateInfo / downloading :
interface UpdateInfo {
version: string;
}
let updateInfo: UpdateInfo | null = null;
On supprime
downloadUrl,assetName,releaseNotes— electron-updater gère tout ça. On supprimedownloading— electron-updater gère l'état.
Step 3: Supprimer tout le bloc update custom
Supprimer entièrement (lignes 254-450) :
compareVersions()- Interfaces
GiteaAsset,GiteaRelease checkForUpdates()startDownload()sendUpdateError()launchUpdater()
Step 4: Écrire le nouveau système de mise à jour
Ajouter à la place du code supprimé :
// ─── COMPARAISON DE VERSIONS ────────────────────────────────────────────────
function compareVersions(a: string, b: string): number {
const pa = a.replace(/^v/, '').split('.').map(Number);
const pb = b.replace(/^v/, '').split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((pb[i] || 0) > (pa[i] || 0)) return 1;
if ((pb[i] || 0) < (pa[i] || 0)) return -1;
}
return 0;
}
// ─── ELECTRON-UPDATER : CONFIGURATION ───────────────────────────────────────
autoUpdater.autoDownload = true;
autoUpdater.autoInstallOnAppQuit = false;
// ─── ELECTRON-UPDATER : EVENTS ──────────────────────────────────────────────
autoUpdater.on('update-available', (info) => {
updateInfo = { version: info.version };
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-available', updateInfo);
}
fireNotification(
`Mise a jour v${info.version} disponible !`,
'Téléchargement en cours...'
);
rebuildTrayMenu();
});
autoUpdater.on('download-progress', (progress) => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-progress', { percent: Math.round(progress.percent) });
}
});
autoUpdater.on('update-downloaded', () => {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-ready', updateInfo ?? {});
}
});
autoUpdater.on('error', (err) => {
console.error('Update error:', err.message);
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-error', { message: err.message });
}
});
// ─── VÉRIFICATION VIA API GITEA ─────────────────────────────────────────────
function checkForUpdates(silent = false): void {
const https = require('https') as typeof import('https');
const options: import('https').RequestOptions = {
hostname: GITEA_HOST,
port: 443,
path: `/api/v1/repos/${GITEA_USER}/${GITEA_REPO}/releases?limit=1`,
method: 'GET',
headers: {
'User-Agent': `MinuteurDragodinde/${CURRENT_VERSION}`,
'Accept': 'application/json',
},
};
const req = https.request(options, (res) => {
let data = '';
res.on('data', (chunk: string) => { data += chunk; });
res.on('end', () => {
try {
const releases = JSON.parse(data);
if (!Array.isArray(releases) || releases.length === 0) return;
const release = releases[0];
const latestVersion = release.tag_name;
if (!latestVersion || compareVersions(CURRENT_VERSION, latestVersion) <= 0) {
if (!silent && mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-not-available');
}
return;
}
// Pointer electron-updater vers le tag de la release
const tag = latestVersion.startsWith('v') ? latestVersion : `v${latestVersion}`;
autoUpdater.setFeedURL({
provider: 'generic',
url: `https://${GITEA_HOST}/${GITEA_USER}/${GITEA_REPO}/releases/download/${tag}`,
});
// electron-updater prend le relais : lit latest.yml, télécharge, vérifie
autoUpdater.checkForUpdates();
} catch (e: unknown) {
console.error('Update check parse error:', (e as Error).message);
}
});
});
req.on('error', (e: Error) => console.error('Update check error:', e.message));
req.end();
}
Step 5: Mettre à jour le handler install-update
Remplacer :
ipcMain.on('install-update', () => startDownload());
Par :
ipcMain.on('install-update', () => {
autoUpdater.quitAndInstall(true, true);
});
quitAndInstall(isSilent, isForceRunAfter): installe en silencieux et relance l'app.
Step 6: Mettre à jour le tray menu
Dans rebuildTrayMenu(), remplacer le click du menu update :
if (updateInfo) {
items.push({ type: 'separator' });
items.push({
label: `⬆ Mise a jour v${updateInfo.version} disponible !`,
click: () => autoUpdater.quitAndInstall(true, true),
});
}
Step 7: Simplifier le bloc did-finish-load
Le bloc did-finish-load (ligne 115-133) reste identique — il envoie déjà update-available si updateInfo existe.
Step 8: Commit
git add src/infrastructure/electron/main.ts
git commit -m "feat: migrate update system to electron-updater"
Task 3: Vérifier que preload.ts et UpdateBanner.ts n'ont pas besoin de changement
Files:
- Read:
src/infrastructure/electron/preload.ts - Read:
src/presentation/components/UpdateBanner.ts
Step 1: Vérifier les IPC channels
Les channels IPC n'ont pas changé :
update-available→{ version }✓update-downloading→ ATTENTION : l'ancien code envoyaitupdate-downloading, mais electron-updater n'a pas cet event. Il passe directement deupdate-availableàdownload-progress. Le banner statedownloadingest activé paronUpdateDownloadingOUonUpdateProgress. Vérifier queUpdateBannergère bien la transition.
Step 2: Envoyer update-downloading explicitement
Dans les events autoUpdater, ajouter après update-available :
autoUpdater.on('update-available', (info) => {
updateInfo = { version: info.version };
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send('update-available', updateInfo);
// Envoyer aussi downloading car autoDownload = true
mainWindow.webContents.send('update-downloading', { version: info.version });
}
fireNotification(
`Mise a jour v${info.version} disponible !`,
'Téléchargement en cours...'
);
rebuildTrayMenu();
});
Cela garantit que
UpdateBannerpasse bien en étatdownloadingcomme avant.
Step 3: Commit (si changement)
git add src/infrastructure/electron/main.ts
git commit -m "fix: send update-downloading event for banner compatibility"
Task 4: Vérifier la génération de latest.yml
Step 1: Lancer un build
npm run build
Step 2: Vérifier que dist/latest.yml existe
ls dist/latest.yml
cat dist/latest.yml
Contenu attendu (exemple) :
version: 1.1.5
files:
- url: Minuteur-Dragodinde-Setup-1.1.5.exe
sha512: <hash>
size: <size>
path: Minuteur-Dragodinde-Setup-1.1.5.exe
sha512: <hash>
releaseDate: '2026-04-04T...'
Step 3: Vérifier que le .exe est aussi généré
ls dist/Minuteur-Dragodinde-Setup-*.exe
Task 5: Mettre à jour le CHANGELOG
Files:
- Modify:
CHANGELOG.md
Step 1: Ajouter l'entrée
Ajouter sous ## v1.1.6 (ou créer ## v1.1.7 si nouvelle version) :
### Mise à jour automatique
- **Migration electron-updater** : remplacement du système custom (download HTTP + script batch) par `electron-updater` (generic provider)
- Vérification sha512 automatique des mises à jour
- Installation NSIS native (plus de script batch hack)
- Restart automatique après installation
- Code simplifié (~40 lignes vs ~200)
- Compatible Gitea : découverte via API + `latest.yml` uploadé en asset de release
Step 2: Commit
git add CHANGELOG.md
git commit -m "docs: add electron-updater migration to CHANGELOG"
Task 6: Mettre à jour le workflow de release
Step 1: Documenter le nouveau process
Le nouveau workflow de release est :
- Mettre à jour
versiondanspackage.json npm run build- Deux fichiers sont générés dans
dist/:Minuteur-Dragodinde-Setup-X.Y.Z.exelatest.yml
- Commit + tag + push :
git add -A && git commit -m "vX.Y.Z" git tag vX.Y.Z && git push && git push --tags - Sur Gitea → Releases → Créer release avec le tag
- Uploader les 2 fichiers : le
.exeETlatest.yml
Important
:
latest.ymlDOIT être uploadé à chaque release, sinon electron-updater ne pourra pas vérifier l'intégrité du fichier.
Récapitulatif des fichiers modifiés
| Fichier | Action |
|---|---|
package.json |
Ajout dep electron-updater + config publish |
package-lock.json |
Mis à jour automatiquement |
src/infrastructure/electron/main.ts |
Réécriture section update (~200 lignes → ~60 lignes) |
src/infrastructure/electron/preload.ts |
Aucun changement |
src/presentation/components/UpdateBanner.ts |
Aucun changement |
CHANGELOG.md |
Entrée migration |