From 981fd62b8f91348126071b5a2d436dbdebfc573b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=ABl?= Date: Sun, 28 Jun 2026 10:24:31 +0200 Subject: [PATCH] feat(desktop): show what's changed in auto-update prompt Publish HEALTHCHAT_CHANGES.md release notes into the update feeds so users see what changed. macOS gets them natively via releases.json's notes field; Windows fetches a published notes.txt, since Squirrel.Windows only delivers releaseName through the update-downloaded event. --- .../workflows/build-healthchat-desktop.yaml | 34 +++++++++++++++++++ apps/desktop/src/updater.ts | 34 +++++++++++++++++-- 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-healthchat-desktop.yaml b/.github/workflows/build-healthchat-desktop.yaml index 7810dd36eb..54476614ae 100644 --- a/.github/workflows/build-healthchat-desktop.yaml +++ b/.github/workflows/build-healthchat-desktop.yaml @@ -231,6 +231,12 @@ jobs: permissions: contents: write steps: + # Needed so the staging step can read HEALTHCHAT_CHANGES.md to extract release notes. + # Lands at the workspace root; download-artifact below writes to dist-artifacts/, no conflict. + - uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Download all build artifacts uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 with: @@ -269,11 +275,36 @@ jobs: - name: Stage update feed + public installers run: | set -euo pipefail + # --- Release notes: extract this version's section from HEALTHCHAT_CHANGES.md --- + # Same extraction as build-healthchat.yaml (web). Surfaced to desktop users in the + # auto-update "See changes" dialog: macOS reads it from releases.json's `notes` + # field natively, Windows fetches notes.txt (Squirrel.Windows can't carry notes + # through the update-downloaded event — only releaseName is available). + VERSION="${GITHUB_REF_NAME#healthchat-v}" + NOTES=$(awk -v ver="$VERSION" ' + /^## Changes in / { + if (found) { exit } + if ($0 ~ "## Changes in " ver "( |$|\\()") { found = 1; next } + } + found { print } + ' HEALTHCHAT_CHANGES.md | sed '/^[[:space:]]*$/{N;/^\n$/D;}') + if [[ -z "$NOTES" ]]; then + NOTES="HealthChat ${VERSION} — automated build from ${GITHUB_REF_NAME}." + echo "::warning::No HEALTHCHAT_CHANGES.md section found for $VERSION — using fallback" + fi + printf '%s\n' "$NOTES" > notes.txt + echo "--- release notes ---"; cat notes.txt + # --- macOS update feed: releases.json (Squirrel.Mac manifest) + signed *-mac.zip payload --- mkdir -p feed/update/macos feed/install/macos test -f dist-artifacts/macos/releases.json || { echo "::error::dist-artifacts/macos/releases.json missing (did the macos job get base-url?)"; exit 1; } cp -v dist-artifacts/macos/releases.json feed/update/macos/ + # Splice the notes into the manifest: Squirrel.Mac maps updateTo.notes -> the + # update-downloaded event's releaseNotes, which the renderer already renders. + jq --rawfile notes notes.txt '.releases[0].updateTo.notes = $notes' \ + feed/update/macos/releases.json > feed/update/macos/releases.json.tmp + mv feed/update/macos/releases.json.tmp feed/update/macos/releases.json find dist-artifacts/macos -type f -iname '*-mac.zip' -exec cp -v {} feed/update/macos/ \; # --- macOS installer: versioned .dmg + a stable "latest" copy --- find dist-artifacts/macos -type f -iname '*.dmg' -exec cp -v {} feed/install/macos/ \; @@ -284,6 +315,9 @@ jobs: src="dist-artifacts/win-$arch" [ -d "$src" ] || { echo "::error::$src missing"; exit 1; } mkdir -p "feed/update/win32/$arch" "feed/install/win32/$arch" + # Side-channel for release notes: the desktop main process fetches this and + # populates releaseNotes itself, since Squirrel.Windows can't deliver them. + cp -v notes.txt "feed/update/win32/$arch/notes.txt" find "$src" -type f -iname 'RELEASES' -exec cp -v {} "feed/update/win32/$arch/" \; find "$src" -type f -iname '*-full.nupkg' -exec cp -v {} "feed/update/win32/$arch/" \; find "$src" -type f -iname '* Setup*.exe' -exec cp -v {} "feed/install/win32/$arch/" \; diff --git a/apps/desktop/src/updater.ts b/apps/desktop/src/updater.ts index 5b35fc5a09..2889a09443 100644 --- a/apps/desktop/src/updater.ts +++ b/apps/desktop/src/updater.ts @@ -78,6 +78,7 @@ export async function start(updateBaseUrl: string): Promise { if (!updateBaseUrl.endsWith("/")) { updateBaseUrl = updateBaseUrl + "/"; } + updateFeedBaseUrl = updateBaseUrl; try { let url: string; @@ -184,6 +185,27 @@ interface ICachedUpdate { updateURL: string; } +// The feed base URL passed to start(), retained so the update-downloaded handler can fetch +// release notes out-of-band on Windows (see below). +let updateFeedBaseUrl: string | undefined; + +// On Windows the autoUpdater never provides release notes: "With Squirrel.Windows only +// releaseName is available" (https://www.electronjs.org/docs/latest/api/auto-updater). We +// publish a notes.txt alongside the per-arch Squirrel feed and fetch it here so the renderer's +// update toast can show "what's changed" the same way it does on macOS (where Squirrel.Mac +// carries the notes natively via releases.json). +async function fetchWindowsReleaseNotes(): Promise { + if (process.platform !== "win32" || !updateFeedBaseUrl) return ""; + try { + const res = await fetch(`${updateFeedBaseUrl}win32/${process.arch}/notes.txt`); + if (!res.ok) return ""; + return await res.text(); + } catch (err) { + console.log("Couldn't fetch Windows release notes", err); + return ""; + } +} + // cache the latest update which has been downloaded as electron offers no api to read it let latestUpdateDownloaded: ICachedUpdate | undefined; autoUpdater @@ -205,7 +227,13 @@ autoUpdater }); autoUpdater.on("update-downloaded", (ev, releaseNotes, releaseName, releaseDate, updateURL) => { - // forward to renderer - latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL }; - global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded); + void (async (): Promise => { + // Windows delivers no release notes via the event, so fetch them out-of-band. + if (!releaseNotes) { + releaseNotes = await fetchWindowsReleaseNotes(); + } + // forward to renderer + latestUpdateDownloaded = { releaseNotes, releaseName, releaseDate, updateURL }; + global.mainWindow?.webContents.send("update-downloaded", latestUpdateDownloaded); + })(); });