Skip to content

Commit 55055dd

Browse files
dbfxclaude
andcommitted
feat: add auto-updater and About view
- Integrate electron-updater with GitHub releases as update source - Add About view showing app version, Electron/Chrome/Node versions, and update status with check/download/install controls - Add info icon to sidebar (pinned to bottom) for About navigation - Update release workflow to generate latest.yml for auto-updater - Upload RELEASES and nupkg alongside exe/zip in GitHub releases Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 93c5b9b commit 55055dd

File tree

9 files changed

+349
-13
lines changed

9 files changed

+349
-13
lines changed

.github/workflows/release.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,31 @@ jobs:
2727
- name: Build installers
2828
run: npm run make
2929

30+
- name: Generate latest.yml for auto-updater
31+
shell: bash
32+
run: |
33+
VERSION=${GITHUB_REF_NAME#v}
34+
SETUP_EXE=$(find out/make -name "*.exe" | head -1)
35+
SETUP_NAME=$(basename "$SETUP_EXE")
36+
SHA512=$(sha512sum "$SETUP_EXE" | awk '{print $1}')
37+
SIZE=$(stat -c%s "$SETUP_EXE")
38+
39+
cat > out/make/latest.yml << EOF
40+
version: ${VERSION}
41+
files:
42+
- url: ${SETUP_NAME}
43+
sha512: ${SHA512}
44+
size: ${SIZE}
45+
path: ${SETUP_NAME}
46+
sha512: ${SHA512}
47+
releaseDate: $(date -u +"%Y-%m-%dT%H:%M:%S.000Z")
48+
EOF
49+
50+
# Remove leading whitespace from heredoc
51+
sed -i 's/^ //' out/make/latest.yml
52+
echo "--- latest.yml ---"
53+
cat out/make/latest.yml
54+
3055
- name: Upload artifacts
3156
uses: actions/upload-artifact@v4
3257
with:
@@ -35,6 +60,7 @@ jobs:
3560
out/make/**/*.exe
3661
out/make/**/*.zip
3762
out/make/**/*.nupkg
63+
out/make/latest.yml
3864
3965
- name: Create GitHub Release
4066
uses: softprops/action-gh-release@v2
@@ -43,5 +69,8 @@ jobs:
4369
files: |
4470
out/make/**/*.exe
4571
out/make/**/*.zip
72+
out/make/**/*.nupkg
73+
out/make/**/RELEASES
74+
out/make/latest.yml
4675
env:
4776
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

package-lock.json

Lines changed: 64 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
},
2929
"dependencies": {
3030
"electron-squirrel-startup": "^1.0.1",
31+
"electron-updater": "^6.8.3",
3132
"framer-motion": "^11.15.0",
3233
"react": "^18.3.1",
3334
"react-dom": "^18.3.1",

src/main/main.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,40 @@
1-
import { app, BrowserWindow } from 'electron';
1+
import { app, BrowserWindow, ipcMain } from 'electron';
22
import path from 'node:path';
33
import started from 'electron-squirrel-startup';
4+
import { autoUpdater } from 'electron-updater';
45
import { registerIpcHandlers, cleanupSession } from './ipc-handlers';
56

67
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
78
if (started) app.quit();
89

910
let mainWindow: BrowserWindow | null = null;
1011

12+
function setupAutoUpdater(win: BrowserWindow) {
13+
autoUpdater.setFeedURL({
14+
provider: 'github',
15+
owner: 'dbfx',
16+
repo: 'modern-win-mtr',
17+
});
18+
autoUpdater.autoDownload = false;
19+
autoUpdater.autoInstallOnAppQuit = true;
20+
21+
const send = (channel: string, data?: unknown) => {
22+
if (!win.isDestroyed()) win.webContents.send(channel, data);
23+
};
24+
25+
autoUpdater.on('checking-for-update', () => send('updater:status', 'checking'));
26+
autoUpdater.on('update-available', () => send('updater:status', 'available'));
27+
autoUpdater.on('update-not-available', () => send('updater:status', 'up-to-date'));
28+
autoUpdater.on('download-progress', (progress) => send('updater:progress', progress.percent));
29+
autoUpdater.on('update-downloaded', () => send('updater:status', 'downloaded'));
30+
autoUpdater.on('error', () => send('updater:status', 'error'));
31+
32+
ipcMain.handle('updater:check', () => autoUpdater.checkForUpdates().catch(() => {}));
33+
ipcMain.handle('updater:download', () => autoUpdater.downloadUpdate().catch(() => {}));
34+
ipcMain.handle('updater:install', () => autoUpdater.quitAndInstall());
35+
ipcMain.handle('app:version', () => app.getVersion());
36+
}
37+
1138
const createWindow = () => {
1239
mainWindow = new BrowserWindow({
1340
width: 1600,
@@ -36,6 +63,7 @@ const createWindow = () => {
3663
}
3764

3865
registerIpcHandlers(mainWindow);
66+
setupAutoUpdater(mainWindow);
3967
};
4068

4169
app.on('ready', createWindow);

src/main/preload.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,20 @@ contextBridge.exposeInMainWorld('mtrApi', {
3434
ipcRenderer.on('loss:data', listener as (...args: unknown[]) => void);
3535
return () => { ipcRenderer.removeListener('loss:data', listener as (...args: unknown[]) => void); };
3636
},
37+
38+
// Auto-updater
39+
getVersion: () => ipcRenderer.invoke('app:version'),
40+
checkForUpdates: () => ipcRenderer.invoke('updater:check'),
41+
downloadUpdate: () => ipcRenderer.invoke('updater:download'),
42+
installUpdate: () => ipcRenderer.invoke('updater:install'),
43+
onUpdaterStatus: (callback: (status: string) => void) => {
44+
const listener = (_: unknown, status: string) => callback(status);
45+
ipcRenderer.on('updater:status', listener as (...args: unknown[]) => void);
46+
return () => { ipcRenderer.removeListener('updater:status', listener as (...args: unknown[]) => void); };
47+
},
48+
onUpdaterProgress: (callback: (percent: number) => void) => {
49+
const listener = (_: unknown, percent: number) => callback(percent);
50+
ipcRenderer.on('updater:progress', listener as (...args: unknown[]) => void);
51+
return () => { ipcRenderer.removeListener('updater:progress', listener as (...args: unknown[]) => void); };
52+
},
3753
});

src/renderer/App.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import Layout from './components/Layout';
44
import TraceView from './components/TraceView';
55
import MapView from './components/MapView';
66
import LossMonitorView from './components/LossMonitorView';
7+
import AboutView from './components/AboutView';
78
import { useMtrSession } from './hooks/useMtrSession';
89
import { useLossMonitor } from './hooks/useLossMonitor';
910

1011
export default function App() {
11-
const [activeView, setActiveView] = useState<'trace' | 'map' | 'loss'>('trace');
12+
const [activeView, setActiveView] = useState<'trace' | 'map' | 'loss' | 'about'>('trace');
1213
const { hops, status, target, resolvedIp, error, start, stop } = useMtrSession();
1314
const lossMonitor = useLossMonitor();
1415

@@ -86,6 +87,18 @@ export default function App() {
8687
/>
8788
</motion.div>
8889
)}
90+
{activeView === 'about' && (
91+
<motion.div
92+
key="about"
93+
initial={{ opacity: 0, y: 8 }}
94+
animate={{ opacity: 1, y: 0 }}
95+
exit={{ opacity: 0, y: -8 }}
96+
transition={{ duration: 0.2 }}
97+
className="h-full"
98+
>
99+
<AboutView />
100+
</motion.div>
101+
)}
89102
</AnimatePresence>
90103
</Layout>
91104
);

0 commit comments

Comments
 (0)