Skip to content

Commit 11c4375

Browse files
committed
Add auto-update via electron-updater and GitHub Releases
1 parent d22cc1d commit 11c4375

File tree

10 files changed

+324
-25
lines changed

10 files changed

+324
-25
lines changed

.github/workflows/build.yml

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323
args: --win --x64
2424

2525
runs-on: ${{ matrix.os }}
26+
permissions:
27+
contents: write
2628

2729
steps:
2830
- uses: actions/checkout@v4
@@ -38,8 +40,8 @@ jobs:
3840
- name: Build app
3941
run: npx electron-vite build
4042

41-
- name: Package executable
42-
run: npx electron-builder ${{ matrix.args }} --publish never
43+
- name: Package and publish
44+
run: npx electron-builder ${{ matrix.args }} --publish always
4345
env:
4446
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4547
CSC_LINK: ${{ matrix.platform == 'mac' && secrets.CSC_LINK || '' }}
@@ -53,30 +55,25 @@ jobs:
5355
name: vbcdr-${{ matrix.platform }}
5456
path: |
5557
dist/*.dmg
58+
dist/*.zip
59+
dist/*.blockmap
5660
dist/*.AppImage
5761
dist/*.deb
5862
dist/*.exe
63+
dist/latest-*.yml
5964
if-no-files-found: error
6065

61-
release:
66+
publish-release:
6267
if: startsWith(github.ref, 'refs/tags/v')
6368
needs: build
6469
runs-on: ubuntu-latest
6570
permissions:
6671
contents: write
6772

6873
steps:
69-
- uses: actions/download-artifact@v4
70-
with:
71-
path: artifacts
74+
- uses: actions/checkout@v4
7275

73-
- name: Create GitHub Release
74-
uses: softprops/action-gh-release@v2
75-
with:
76-
draft: false
77-
generate_release_notes: true
78-
files: |
79-
artifacts/vbcdr-mac/*.dmg
80-
artifacts/vbcdr-linux/*.AppImage
81-
artifacts/vbcdr-linux/*.deb
82-
artifacts/vbcdr-win/*.exe
76+
- name: Publish release
77+
run: gh release edit "${{ github.ref_name }}" --draft=false
78+
env:
79+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

package-lock.json

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

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@
1919
"build": {
2020
"appId": "com.vbcdr.app",
2121
"productName": "vbcdr",
22+
"publish": {
23+
"provider": "github",
24+
"owner": "jestersimpps",
25+
"repo": "vbcdr-electron"
26+
},
2227
"directories": {
2328
"output": "dist"
2429
},
@@ -35,7 +40,8 @@
3540
"entitlements": "resources/entitlements.mac.plist",
3641
"entitlementsInherit": "resources/entitlements.mac.plist",
3742
"target": [
38-
"dmg"
43+
"dmg",
44+
"zip"
3945
],
4046
"notarize": true
4147
},
@@ -88,6 +94,7 @@
8894
"@xterm/xterm": "^5.5.0",
8995
"chokidar": "^4.0.3",
9096
"electron-store": "^8.2.0",
97+
"electron-updater": "^6.7.3",
9198
"ignore": "^7.0.3",
9299
"node-pty": "^1.0.0",
93100
"react": "^18.3.1",

src/main/index.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,18 @@ import { registerClaudeConfigHandlers } from '@main/ipc/claude-config'
1010
import { killAll, killOrphanedPtys } from '@main/services/pty-manager'
1111
import { stopWatching } from '@main/services/file-watcher'
1212
import { detachAllTabs } from '@main/services/browser-view'
13+
import { registerUpdaterHandlers } from '@main/ipc/updater'
14+
import { initAutoUpdater, checkForUpdates, checkForUpdatesInteractive } from '@main/services/auto-updater'
1315

1416
app.setName('vbcdr')
17+
app.setAboutPanelOptions({
18+
applicationName: 'vbcdr',
19+
applicationVersion: app.getVersion(),
20+
version: '',
21+
copyright: '© 2025 Jo Vinkenroye',
22+
credits: 'A desktop vibe coding environment for Claude Code developers.\nTerminal, browser, editor, and git — all in one window.',
23+
iconPath: path.join(__dirname, '../../resources/icon.png')
24+
})
1525

1626
const CHROME_USER_AGENT =
1727
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36'
@@ -74,6 +84,7 @@ registerBrowserHandlers()
7484
registerGitHandlers()
7585
registerPasswordHandlers()
7686
registerClaudeConfigHandlers()
87+
registerUpdaterHandlers()
7788

7889
function buildMenu(): Electron.MenuItemConstructorOptions[] {
7990
const isMac = process.platform === 'darwin'
@@ -88,6 +99,10 @@ function buildMenu(): Electron.MenuItemConstructorOptions[] {
8899
accelerator: 'CmdOrCtrl+,',
89100
click: () => mainWindow?.webContents.send('menu:action', 'settings')
90101
},
102+
{
103+
label: 'Check for Updates...',
104+
click: () => checkForUpdatesInteractive()
105+
},
91106
{ type: 'separator' },
92107
{ role: 'hide', label: 'Hide vbcdr' },
93108
{ role: 'hideOthers' },
@@ -225,6 +240,11 @@ app.whenReady().then(() => {
225240
createWindow()
226241
Menu.setApplicationMenu(Menu.buildFromTemplate(buildMenu()))
227242

243+
initAutoUpdater()
244+
if (!process.env.ELECTRON_RENDERER_URL) {
245+
setTimeout(() => checkForUpdates(), 5000)
246+
}
247+
228248
app.on('web-contents-created', (_event, contents) => {
229249
if (contents.getType() === 'webview') {
230250
const ses = contents.session

src/main/ipc/updater.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { ipcMain } from 'electron'
2+
import { checkForUpdates, quitAndInstall, getUpdateStatus } from '@main/services/auto-updater'
3+
4+
export function registerUpdaterHandlers(): void {
5+
ipcMain.handle('updater:check', () => {
6+
checkForUpdates()
7+
})
8+
9+
ipcMain.handle('updater:install', () => {
10+
quitAndInstall()
11+
})
12+
13+
ipcMain.handle('updater:status', () => {
14+
return getUpdateStatus()
15+
})
16+
}

src/main/services/auto-updater.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { autoUpdater, UpdateInfo, ProgressInfo } from 'electron-updater'
2+
import { BrowserWindow, dialog } from 'electron'
3+
4+
export type UpdateState = 'idle' | 'checking' | 'available' | 'not-available' | 'downloading' | 'downloaded' | 'error'
5+
6+
export interface UpdateStatus {
7+
state: UpdateState
8+
version?: string
9+
percent?: number
10+
error?: string
11+
}
12+
13+
let currentStatus: UpdateStatus = { state: 'idle' }
14+
15+
function broadcast(status: UpdateStatus): void {
16+
currentStatus = status
17+
for (const win of BrowserWindow.getAllWindows()) {
18+
win.webContents.send('updater:status', status)
19+
}
20+
}
21+
22+
export function initAutoUpdater(): void {
23+
autoUpdater.autoDownload = true
24+
autoUpdater.autoInstallOnAppQuit = true
25+
26+
autoUpdater.on('checking-for-update', () => {
27+
broadcast({ state: 'checking' })
28+
})
29+
30+
autoUpdater.on('update-available', (info: UpdateInfo) => {
31+
broadcast({ state: 'available', version: info.version })
32+
})
33+
34+
autoUpdater.on('update-not-available', () => {
35+
broadcast({ state: 'not-available' })
36+
})
37+
38+
autoUpdater.on('download-progress', (progress: ProgressInfo) => {
39+
broadcast({ state: 'downloading', percent: Math.round(progress.percent) })
40+
})
41+
42+
autoUpdater.on('update-downloaded', (info: UpdateInfo) => {
43+
broadcast({ state: 'downloaded', version: info.version })
44+
})
45+
46+
autoUpdater.on('error', (err: Error) => {
47+
broadcast({ state: 'error', error: err.message })
48+
})
49+
}
50+
51+
export function checkForUpdates(): void {
52+
autoUpdater.checkForUpdates()
53+
}
54+
55+
export async function checkForUpdatesInteractive(): Promise<void> {
56+
const result = await autoUpdater.checkForUpdates().catch((err: Error) => {
57+
dialog.showMessageBox({
58+
type: 'error',
59+
title: 'Update Error',
60+
message: 'Could not check for updates',
61+
detail: err.message
62+
})
63+
return null
64+
})
65+
66+
if (!result) return
67+
68+
if (result.updateInfo.version === autoUpdater.currentVersion.version) {
69+
dialog.showMessageBox({
70+
type: 'info',
71+
title: 'No Updates',
72+
message: 'You\'re on the latest version',
73+
detail: `vbcdr v${autoUpdater.currentVersion.version}`
74+
})
75+
}
76+
}
77+
78+
export function quitAndInstall(): void {
79+
autoUpdater.quitAndInstall()
80+
}
81+
82+
export function getUpdateStatus(): UpdateStatus {
83+
return currentStatus
84+
}

src/preload/index.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,17 @@ const api = {
118118
return () => ipcRenderer.removeListener('menu:action', handler)
119119
},
120120

121+
updater: {
122+
check: () => ipcRenderer.invoke('updater:check'),
123+
install: () => ipcRenderer.invoke('updater:install'),
124+
getStatus: () => ipcRenderer.invoke('updater:status'),
125+
onStatus: (callback: (status: unknown) => void) => {
126+
const handler = (_event: Electron.IpcRendererEvent, status: unknown) => callback(status)
127+
ipcRenderer.on('updater:status', handler)
128+
return () => ipcRenderer.removeListener('updater:status', handler)
129+
}
130+
},
131+
121132
passwords: {
122133
save: (projectId: string, domain: string, username: string, password: string) =>
123134
ipcRenderer.invoke('passwords:save', projectId, domain, username, password),

0 commit comments

Comments
 (0)