Files
2026-06-19 11:37:35 +02:00

163 lines
5.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Publie les artefacts du launcher sur deux releases Gitea :
*
* 1. Release à tag fixe "latest" — electron-updater (provider generic) lit
* latest.yml à cette URL pour proposer les mises à jour automatiques.
*
* 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_TOKEN token write:repository (obligatoire)
*/
import { readdir, readFile } from 'node:fs/promises'
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 TOKEN = process.env.GITEA_TOKEN
const DIST = join(process.cwd(), 'dist')
const API = `${BASE}/api/v1`
if (!TOKEN) {
console.error('GITEA_TOKEN manquant (scope write:repository).')
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.
* - Windows : latest.yml + installeur NSIS (+ .blockmap)
* - Linux : latest-linux.yml + AppImage (+ .blockmap) + .deb
*/
function isUpdateArtifact(name) {
return (
name === 'latest.yml' ||
name === 'latest-linux.yml' ||
name.endsWith('-setup.exe') ||
name.endsWith('-setup.exe.blockmap') ||
name.endsWith('.AppImage') ||
name.endsWith('.AppImage.blockmap') ||
name.endsWith('.deb')
)
}
async function api(path, init = {}) {
const res = await fetch(`${API}${path}`, {
...init,
headers: {
Authorization: `token ${TOKEN}`,
Accept: 'application/json',
...(init.headers ?? {})
}
})
if (!res.ok) {
const body = await res.text().catch(() => '')
throw new Error(`Gitea ${init.method ?? 'GET'} ${path}${res.status} ${body}`)
}
return res.status === 204 ? null : res.json()
}
/** 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/${tag}${res.status} ${await res.text()}`)
}
console.log(`Création de la release "${tag}"…`)
return api(`/repos/${OWNER}/${REPO}/releases`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
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}"…`)
await api(`/repos/${OWNER}/${REPO}/releases/${releaseId}/assets/${a.id}`, {
method: 'DELETE'
})
}
}
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`.")
}
console.log(`Version : ${versionTag}`)
console.log(`Artefacts : ${files.join(', ')}`)
console.log(`Dépôt : ${OWNER}/${REPO}`)
// 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
)
// 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) => {
console.error(e.message)
process.exit(1)
})