v0.8.0 — Update-available detection (version chip + changelog)
Footer becomes a changelog chip and shows an 'Update available' badge when a newer APP_VERSION exists upstream. Detection reads src/lib/version.ts from the Gitea default branch (dms model) and compares — self-contained, no OTM control plane. Adds src/lib/update-check.ts and GET /api/version (also a running-version probe for the upcoming one-click rebuild). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,13 @@ export type ChangelogEntry = {
|
||||
};
|
||||
|
||||
export const CHANGELOG: ChangelogEntry[] = [
|
||||
{
|
||||
version: "0.8.0",
|
||||
date: "2026-06-15",
|
||||
changes: [
|
||||
"The app now tells you when a newer version is available — a little \"Update available\" badge appears in the bottom bar, and the version number opens the full changelog. (One-click updating is coming next.)",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "0.7.0",
|
||||
date: "2026-06-15",
|
||||
|
||||
60
src/lib/update-check.ts
Normal file
60
src/lib/update-check.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { APP_VERSION } from "./version";
|
||||
|
||||
// Detects whether a newer version has been pushed upstream by reading
|
||||
// src/lib/version.ts from the Gitea repo's default branch and comparing its
|
||||
// APP_VERSION to the running one. Self-contained — no OTM control plane needed.
|
||||
// Reuses the same GITEA_* env as the suggestions module.
|
||||
const GITEA_URL = process.env.GITEA_URL;
|
||||
const GITEA_TOKEN = process.env.GITEA_TOKEN;
|
||||
const GITEA_REPO = process.env.GITEA_REPO;
|
||||
const GITEA_BRANCH = process.env.GITEA_BRANCH || "master";
|
||||
|
||||
export type UpdateStatus = {
|
||||
version: string;
|
||||
latest: string | null;
|
||||
updateAvailable: boolean;
|
||||
};
|
||||
|
||||
/** Read APP_VERSION from src/lib/version.ts on the upstream branch. null on any failure. */
|
||||
export async function fetchUpstreamVersion(): Promise<string | null> {
|
||||
if (!GITEA_URL || !GITEA_TOKEN || !GITEA_REPO) return null;
|
||||
try {
|
||||
const url = `${GITEA_URL}/api/v1/repos/${GITEA_REPO}/contents/src/lib/version.ts?ref=${GITEA_BRANCH}`;
|
||||
const res = await fetch(url, {
|
||||
headers: { Authorization: `token ${GITEA_TOKEN}` },
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!res.ok) return null;
|
||||
const json = await res.json();
|
||||
if (!json?.content) return null;
|
||||
const decoded = Buffer.from(
|
||||
json.content,
|
||||
json.encoding === "base64" ? "base64" : "utf8"
|
||||
).toString("utf8");
|
||||
const m = decoded.match(/APP_VERSION\s*=\s*["']([^"']+)["']/);
|
||||
return m ? m[1] : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Numeric dotted compare. -1 if a<b, 0 if equal, 1 if a>b. */
|
||||
export function compareVersions(a: string, b: string): number {
|
||||
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
||||
const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
|
||||
const len = Math.max(pa.length, pb.length);
|
||||
for (let i = 0; i < len; i++) {
|
||||
const d = (pa[i] || 0) - (pb[i] || 0);
|
||||
if (d !== 0) return d > 0 ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export async function getUpdateStatus(): Promise<UpdateStatus> {
|
||||
const latest = await fetchUpstreamVersion();
|
||||
return {
|
||||
version: APP_VERSION,
|
||||
latest,
|
||||
updateAvailable: latest != null && compareVersions(latest, APP_VERSION) > 0,
|
||||
};
|
||||
}
|
||||
@@ -1,2 +1,2 @@
|
||||
// Bump on every user-visible release. Changelog entries live in `src/lib/changelog.ts`.
|
||||
export const APP_VERSION = "0.7.0";
|
||||
export const APP_VERSION = "0.8.0";
|
||||
|
||||
Reference in New Issue
Block a user