From 3a9a555f193bf3f36adf2c19d630e908f6112aa7 Mon Sep 17 00:00:00 2001 From: lucasdpt Date: Fri, 19 Jun 2026 11:24:26 +0200 Subject: [PATCH] fix: fix release --- .gitea/workflows/publish.yml | 1 - electron-builder.yml | 4 +- scripts/publish-gitea.mjs | 116 ++++++++++++++++++++++------------- src/main/updater.ts | 8 ++- 4 files changed, 83 insertions(+), 46 deletions(-) diff --git a/.gitea/workflows/publish.yml b/.gitea/workflows/publish.yml index a171daa..f6ce4ea 100644 --- a/.gitea/workflows/publish.yml +++ b/.gitea/workflows/publish.yml @@ -55,4 +55,3 @@ jobs: GITEA_URL: ${{ github.server_url }} GITEA_OWNER: ${{ github.repository_owner }} GITEA_REPO: ${{ github.event.repository.name }} - GITEA_TAG: latest diff --git a/electron-builder.yml b/electron-builder.yml index 31fa634..c1593dc 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -31,7 +31,9 @@ linux: - deb maintainer: oflauncher category: Game - artifactName: ${productName}-${version}.${ext} + # Pas de version dans le nom : OFLauncher.AppImage est toujours écrasé sur + # place par l'auto-update, les raccourcis bureau restent valides après maj. + artifactName: ${productName}.${ext} # Publication des binaires du launcher (auto-update). # Provider "generic" : electron-updater lit latest.yml à cette URL fixe. # Les artefacts (latest.yml + installeur + .blockmap) sont uploadés sur une diff --git a/scripts/publish-gitea.mjs b/scripts/publish-gitea.mjs index b288a97..be858a6 100644 --- a/scripts/publish-gitea.mjs +++ b/scripts/publish-gitea.mjs @@ -1,18 +1,18 @@ #!/usr/bin/env node /** - * Publie les artefacts d'auto-update du launcher sur une release Gitea à tag - * fixe ("latest"). electron-updater (provider generic) lit ensuite latest.yml - * à l'URL configurée dans electron-builder.yml. + * Publie les artefacts du launcher sur deux releases Gitea : * - * Pré-requis : - * - `npm run build:win` a produit dist/ (latest.yml + installeur + .blockmap) - * - variable d'env GITEA_TOKEN (scope write:repository) + * 1. Release à tag fixe "latest" — electron-updater (provider generic) lit + * latest.yml à cette URL pour proposer les mises à jour automatiques. * - * Config par variables d'env (avec valeurs par défaut) : + * 2. Release versionnée "vX.Y.Z" — archive permanente consultable dans l'UI + * Gitea, utile pour le suivi des versions et les téléchargements manuels. + * + * Config par variables d'env : * GITEA_URL base de l'instance (def. https://gitea.ldpt.fr) * GITEA_OWNER propriétaire du repo (def. zertus) * GITEA_REPO nom du repo launcher (def. OFLauncher) - * GITEA_TAG tag fixe de la release (def. latest) + * GITEA_TOKEN token write:repository (obligatoire) */ import { readdir, readFile } from 'node:fs/promises' import { join } from 'node:path' @@ -20,7 +20,6 @@ import { join } from 'node:path' const BASE = process.env.GITEA_URL ?? 'https://gitea.ldpt.fr' const OWNER = process.env.GITEA_OWNER ?? 'zertus' const REPO = process.env.GITEA_REPO ?? 'OFLauncher' -const TAG = process.env.GITEA_TAG ?? 'latest' const TOKEN = process.env.GITEA_TOKEN const DIST = join(process.cwd(), 'dist') @@ -31,12 +30,16 @@ if (!TOKEN) { process.exit(1) } +/** Lit la version courante depuis package.json. */ +async function readVersion() { + const pkg = JSON.parse(await readFile(join(process.cwd(), 'package.json'), 'utf8')) + return pkg.version // ex. "3.1.0" +} + /** - * Fichiers de dist/ à publier pour l'auto-update. - * - Windows : latest.yml + l'installeur NSIS (+ .blockmap) - * - Linux : latest-linux.yml + l'AppImage (+ .blockmap) ; le .deb est publié - * pour téléchargement manuel (electron-updater ne l'utilise pas). - * electron-updater choisit le bon latest*.yml selon la plateforme. + * Fichiers de dist/ à publier. + * - Windows : latest.yml + installeur NSIS (+ .blockmap) + * - Linux : latest-linux.yml + AppImage (+ .blockmap) + .deb */ function isUpdateArtifact(name) { return ( @@ -61,65 +64,96 @@ async function api(path, init = {}) { }) if (!res.ok) { const body = await res.text().catch(() => '') - throw new Error(`Gitea ${init.method ?? 'GET'} ${path} -> ${res.status} ${body}`) + throw new Error(`Gitea ${init.method ?? 'GET'} ${path} → ${res.status} ${body}`) } return res.status === 204 ? null : res.json() } -/** Récupère la release au tag fixe, ou la crée si absente. */ -async function getOrCreateRelease() { - const res = await fetch(`${API}/repos/${OWNER}/${REPO}/releases/tags/${TAG}`, { +/** Récupère la release par tag, ou la crée avec les options données. */ +async function getOrCreateRelease(tag, createBody) { + const res = await fetch(`${API}/repos/${OWNER}/${REPO}/releases/tags/${tag}`, { headers: { Authorization: `token ${TOKEN}`, Accept: 'application/json' } }) if (res.ok) return res.json() if (res.status !== 404) { - throw new Error(`Gitea GET release -> ${res.status} ${await res.text()}`) + throw new Error(`Gitea GET release/${tag} → ${res.status} ${await res.text()}`) } - console.log(`Création de la release "${TAG}"…`) + console.log(`Création de la release "${tag}"…`) return api(`/repos/${OWNER}/${REPO}/releases`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - tag_name: TAG, - name: 'Dernière version', - body: 'Artefacts d’auto-update du launcher (écrasés à chaque publication).' - }) + body: JSON.stringify(createBody) }) } async function deleteExistingAssets(releaseId) { const assets = await api(`/repos/${OWNER}/${REPO}/releases/${releaseId}/assets`) for (const a of assets) { - console.log(`Suppression de l'ancien asset ${a.name}…`) + console.log(` Suppression de l'ancien asset "${a.name}"…`) await api(`/repos/${OWNER}/${REPO}/releases/${releaseId}/assets/${a.id}`, { method: 'DELETE' }) } } -async function uploadAsset(releaseId, name) { - const buf = await readFile(join(DIST, name)) - const form = new FormData() - form.append('attachment', new Blob([buf]), name) - console.log(`Upload de ${name} (${(buf.length / 1e6).toFixed(1)} Mo)…`) - await api( - `/repos/${OWNER}/${REPO}/releases/${releaseId}/assets?name=${encodeURIComponent(name)}`, - { method: 'POST', body: form } - ) +async function uploadAssets(releaseId, files) { + for (const name of files) { + const buf = await readFile(join(DIST, name)) + const form = new FormData() + form.append('attachment', new Blob([buf]), name) + console.log(` Upload "${name}" (${(buf.length / 1e6).toFixed(1)} Mo)…`) + await api( + `/repos/${OWNER}/${REPO}/releases/${releaseId}/assets?name=${encodeURIComponent(name)}`, + { method: 'POST', body: form } + ) + } +} + +async function publishRelease(tag, createBody, files) { + console.log(`\n── Release "${tag}" ──`) + const release = await getOrCreateRelease(tag, createBody) + await deleteExistingAssets(release.id) + await uploadAssets(release.id, files) + console.log(` ✓ Release "${tag}" publiée.`) } async function main() { + const version = await readVersion() + const versionTag = `v${version}` + const files = (await readdir(DIST)).filter(isUpdateArtifact) if (!files.some((f) => f === 'latest.yml')) { - throw new Error('dist/latest.yml introuvable — lance d’abord `npm run build:win`.') + throw new Error('dist/latest.yml introuvable — lance d'abord `npm run build:win`.') } - console.log(`Publication sur ${OWNER}/${REPO} (tag ${TAG}) : ${files.join(', ')}`) + console.log(`Version : ${versionTag}`) + console.log(`Artefacts : ${files.join(', ')}`) + console.log(`Dépôt : ${OWNER}/${REPO}`) - const release = await getOrCreateRelease() - await deleteExistingAssets(release.id) - for (const name of files) await uploadAsset(release.id, name) + // 1. Release fixe "latest" — point d'ancrage de l'auto-update. + await publishRelease( + 'latest', + { + tag_name: 'latest', + name: 'Auto-update (dernière version)', + body: 'Artefacts d'auto-update écrasés à chaque publication. Version courante : ' + versionTag, + prerelease: false + }, + files + ) - console.log('✓ Publication terminée.') + // 2. Release versionnée — archive permanente par version. + await publishRelease( + versionTag, + { + tag_name: versionTag, + name: `OFLauncher ${versionTag}`, + body: '', + prerelease: false + }, + files + ) + + console.log('\n✓ Publication terminée.') } main().catch((e) => { diff --git a/src/main/updater.ts b/src/main/updater.ts index da656a6..d473c73 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -24,10 +24,12 @@ export function initUpdater(): void { return } - // On gère l'install manuellement (bouton "Redémarrer"), mais on installe - // quand même à la fermeture si la maj a été téléchargée. autoUpdater.autoDownload = true - autoUpdater.autoInstallOnAppQuit = true + // Sur Linux (AppImage), l'install automatique au quit utilise execFileSync + // et bloque le processus en attendant que le nouveau AppImage se ferme — + // comportement très inattendu. On désactive : l'utilisateur clique le bouton + // "Redémarrer pour installer" qui, lui, utilise spawnLog (async) et fonctionne. + autoUpdater.autoInstallOnAppQuit = process.platform !== 'linux' autoUpdater.on('checking-for-update', () => { emit.updateStatus({ state: 'checking' })