ebc33a78b7
- server/: Node bridge (datarefs/commands, navdata, CIFP procedures, flight plan) - web/: React cockpit (PFD/MFD/Map, VFR six-pack, AFCS, FMS CDU), PWA, collapsible sidebar - desktop/: Tauri 2 launcher (Bun sidecar, system tray, updater) + Linux build via Docker - scripts/: prep-desktop, build-linux, Gitea release + latest.json Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
5.5 KiB
JavaScript
120 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
// Publish a release to Gitea and (re)write the updater's latest.json.
|
|
//
|
|
// Reads the built artifacts (macOS .app.tar.gz + .sig, Linux .AppImage + .sig,
|
|
// plus .dmg / .deb for manual install), creates a versioned release with those
|
|
// assets, then builds latest.json (Tauri v2 updater format) pointing at the
|
|
// release download URLs and uploads it to a fixed-tag "updater" release so the
|
|
// app's updater endpoint URL stays constant.
|
|
//
|
|
// Env: GITEA_URL (e.g. https://git.kgva.ch), GITEA_REPO (owner/name),
|
|
// GITEA_TOKEN (or a token file at /tmp/gitea_token).
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
|
|
const ROOT = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..');
|
|
const GITEA_URL = (process.env.GITEA_URL || 'https://git.kgva.ch').replace(/\/$/, '');
|
|
const REPO = process.env.GITEA_REPO || 'karim/xplane-cockpit';
|
|
const TOKEN = process.env.GITEA_TOKEN
|
|
|| (fs.existsSync('/tmp/gitea_token') ? fs.readFileSync('/tmp/gitea_token', 'utf8').trim() : '');
|
|
const VERSION = JSON.parse(fs.readFileSync(path.join(ROOT, 'desktop/src-tauri/tauri.conf.json'), 'utf8')).version;
|
|
const UPDATER_TAG = 'updater';
|
|
|
|
if (!TOKEN) { console.error('No GITEA_TOKEN (env or /tmp/gitea_token).'); process.exit(1); }
|
|
|
|
const api = (p, opts = {}) => fetch(`${GITEA_URL}/api/v1${p}`, {
|
|
...opts,
|
|
headers: { Authorization: `token ${TOKEN}`, Accept: 'application/json', ...(opts.headers || {}) },
|
|
});
|
|
|
|
// Collect built artifacts that exist (mac and/or linux builds may have run).
|
|
function findArtifacts() {
|
|
const macBundle = path.join(ROOT, 'desktop/src-tauri/target/aarch64-apple-darwin/release/bundle');
|
|
const linBundle = path.join(ROOT, 'target-linux/x86_64-unknown-linux-gnu/release/bundle');
|
|
const out = { assets: [], updater: {} };
|
|
const add = (file, platformKey) => {
|
|
if (!fs.existsSync(file)) return;
|
|
out.assets.push(file);
|
|
if (platformKey) {
|
|
const sig = file + '.sig';
|
|
if (fs.existsSync(sig)) out.updater[platformKey] = { file, sig: fs.readFileSync(sig, 'utf8').trim() };
|
|
}
|
|
};
|
|
const glob1 = (dir, re) => fs.existsSync(dir) ? fs.readdirSync(dir).filter((f) => re.test(f)).map((f) => path.join(dir, f)) : [];
|
|
// macOS
|
|
glob1(path.join(macBundle, 'macos'), /\.app\.tar\.gz$/).forEach((f) => add(f, 'darwin-aarch64'));
|
|
glob1(path.join(macBundle, 'dmg'), /\.dmg$/).forEach((f) => out.assets.push(f));
|
|
// Linux
|
|
glob1(path.join(linBundle, 'appimage'), /\.AppImage$/).forEach((f) => add(f, 'linux-x86_64'));
|
|
glob1(path.join(linBundle, 'deb'), /\.deb$/).forEach((f) => out.assets.push(f));
|
|
return out;
|
|
}
|
|
|
|
async function getReleaseByTag(tag) {
|
|
const r = await api(`/repos/${REPO}/releases/tags/${encodeURIComponent(tag)}`);
|
|
return r.ok ? r.json() : null;
|
|
}
|
|
|
|
async function ensureRelease(tag, name, body) {
|
|
let rel = await getReleaseByTag(tag);
|
|
if (rel) return rel;
|
|
const r = await api(`/repos/${REPO}/releases`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ tag_name: tag, name, body, draft: false, prerelease: false }),
|
|
});
|
|
if (!r.ok) throw new Error(`create release ${tag}: ${r.status} ${await r.text()}`);
|
|
return r.json();
|
|
}
|
|
|
|
async function uploadAsset(rel, file, asName) {
|
|
const name = asName || path.basename(file);
|
|
// delete an existing asset with the same name first (Gitea won't overwrite)
|
|
for (const a of rel.assets || []) {
|
|
if (a.name === name) await api(`/repos/${REPO}/releases/${rel.id}/assets/${a.id}`, { method: 'DELETE' });
|
|
}
|
|
const blob = new Blob([fs.readFileSync(file)]);
|
|
const fd = new FormData();
|
|
fd.append('attachment', blob, name);
|
|
const r = await api(`/repos/${REPO}/releases/${rel.id}/assets?name=${encodeURIComponent(name)}`, { method: 'POST', body: fd });
|
|
if (!r.ok) throw new Error(`upload ${name}: ${r.status} ${await r.text()}`);
|
|
console.log(' ↑', name);
|
|
return r.json();
|
|
}
|
|
|
|
const dlUrl = (tag, name) => `${GITEA_URL}/${REPO}/releases/download/${encodeURIComponent(tag)}/${encodeURIComponent(name)}`;
|
|
|
|
async function main() {
|
|
const { assets, updater } = findArtifacts();
|
|
if (!assets.length) { console.error('No build artifacts found — run the builds first.'); process.exit(1); }
|
|
console.log(`Release v${VERSION} → ${GITEA_URL}/${REPO}`);
|
|
console.log('Artifacts:', assets.map((a) => path.basename(a)).join(', '));
|
|
|
|
const verTag = `v${VERSION}`;
|
|
const rel = await ensureRelease(verTag, `X-Plane Cockpit ${verTag}`, `Automated release ${verTag}.`);
|
|
for (const f of assets) await uploadAsset(rel, f);
|
|
const relFresh = await getReleaseByTag(verTag); // refresh asset list
|
|
|
|
// Build latest.json referencing this release's updater artifacts.
|
|
const platforms = {};
|
|
for (const [key, { file, sig }] of Object.entries(updater)) {
|
|
platforms[key] = { signature: sig, url: dlUrl(verTag, path.basename(file)) };
|
|
}
|
|
const latest = {
|
|
version: VERSION,
|
|
notes: `X-Plane Cockpit ${verTag}`,
|
|
pub_date: new Date().toISOString(),
|
|
platforms,
|
|
};
|
|
const latestPath = path.join(ROOT, 'desktop/latest.json');
|
|
fs.writeFileSync(latestPath, JSON.stringify(latest, null, 2));
|
|
console.log('latest.json platforms:', Object.keys(platforms).join(', ') || '(none)');
|
|
|
|
// Upload latest.json to the fixed "updater" release (constant endpoint URL).
|
|
const upd = await ensureRelease(UPDATER_TAG, 'Updater channel', 'Rolling pointer used by the in-app updater.');
|
|
await uploadAsset(upd, latestPath, 'latest.json');
|
|
console.log('Updater endpoint:', dlUrl(UPDATER_TAG, 'latest.json'));
|
|
}
|
|
|
|
main().catch((e) => { console.error(e); process.exit(1); });
|