Skip to content

Commit 1fac3bb

Browse files
feat: auto-updater via GitHub Releases (in-app, user-confirmed) (#10)
* build: add electron-updater dependency + github publish config Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): pure wireUpdater event→payload mapping with tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): register electron-updater (packaged-only) and forward to renderer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): expose update events + actions to the renderer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(update): in-app update banner (available/downloading/ready) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * i18n: auto-update banner strings (ko/en/ja/zh) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(update): re-localize the update banner on language change Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Si Hyeong Lee <writingdeveloper@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 8d16bbe commit 1fac3bb

15 files changed

Lines changed: 339 additions & 20 deletions

File tree

package-lock.json

Lines changed: 101 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: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,19 @@
77
"license": "MIT",
88
"author": "Si Hyeong Lee",
99
"homepage": "https://github.com/writingdeveloper/devdeck#readme",
10-
"repository": { "type": "git", "url": "https://github.com/writingdeveloper/devdeck.git" },
11-
"keywords": ["claude-code", "claude", "electron", "developer-tools", "git", "dashboard", "productivity"],
10+
"repository": {
11+
"type": "git",
12+
"url": "https://github.com/writingdeveloper/devdeck.git"
13+
},
14+
"keywords": [
15+
"claude-code",
16+
"claude",
17+
"electron",
18+
"developer-tools",
19+
"git",
20+
"dashboard",
21+
"productivity"
22+
],
1223
"scripts": {
1324
"build": "tsc && node scripts/build-renderer.mjs && node scripts/copy-assets.mjs",
1425
"dist": "npm run build && electron-builder --win --dir",
@@ -30,8 +41,18 @@
3041
"directories": {
3142
"output": "release"
3243
},
44+
"publish": [
45+
{ "provider": "github", "owner": "writingdeveloper", "repo": "devdeck" }
46+
],
3347
"win": {
34-
"target": [{ "target": "nsis", "arch": ["x64"] }],
48+
"target": [
49+
{
50+
"target": "nsis",
51+
"arch": [
52+
"x64"
53+
]
54+
}
55+
],
3556
"icon": "build/icon.ico"
3657
},
3758
"nsis": {
@@ -41,13 +62,24 @@
4162
"artifactName": "${productName}-${version}-Setup.${ext}"
4263
},
4364
"mac": {
44-
"target": [{ "target": "dmg", "arch": ["arm64", "x64"] }],
65+
"target": [
66+
{
67+
"target": "dmg",
68+
"arch": [
69+
"arm64",
70+
"x64"
71+
]
72+
}
73+
],
4574
"category": "public.app-category.developer-tools",
4675
"icon": "build/icon.png",
4776
"artifactName": "${productName}-${version}-${arch}.${ext}"
4877
},
4978
"linux": {
50-
"target": ["AppImage", "deb"],
79+
"target": [
80+
"AppImage",
81+
"deb"
82+
],
5183
"category": "Development",
5284
"icon": "build/icon.png",
5385
"maintainer": "writingdeveloper <writingdeveloper@users.noreply.github.com>",
@@ -63,5 +95,8 @@
6395
"playwright": "^1.60.0",
6496
"typescript": "^5.5.0",
6597
"vitest": "^2.0.0"
98+
},
99+
"dependencies": {
100+
"electron-updater": "^6.8.9"
66101
}
67102
}

src/main/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as path from 'node:path';
33
import { Store } from './store';
44
import { registerIpc } from './ipc';
55
import { setupTray } from './tray';
6+
import { registerUpdater } from './updater';
67

78
let win: BrowserWindow | null = null;
89

@@ -52,6 +53,7 @@ if (!gotLock) {
5253
defaultLanguage: app.getLocale().split('-')[0] || 'en',
5354
});
5455
setupTray(w);
56+
registerUpdater(w);
5557
globalShortcut.register('Control+Alt+D', showWindow);
5658
app.on('activate', () => { if (!win) win = createWindow(); });
5759
});

src/main/updater.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { app, ipcMain, type BrowserWindow } from 'electron';
2+
import { wireUpdater } from '../shared/update';
3+
4+
/** Wire electron-updater to the renderer. Inert in dev (unpackaged). Errors are logged, never surfaced. */
5+
export function registerUpdater(win: BrowserWindow): void {
6+
if (!app.isPackaged) return;
7+
let autoUpdater: unknown;
8+
try {
9+
({ autoUpdater } = require('electron-updater'));
10+
} catch {
11+
return; // dependency unavailable — skip silently
12+
}
13+
const api = wireUpdater(
14+
autoUpdater as Parameters<typeof wireUpdater>[0],
15+
(p) => win.webContents.send('devdeck:update', p),
16+
(e) => console.error('DevDeck update:', e),
17+
);
18+
ipcMain.handle('update:download', () => api.download());
19+
ipcMain.handle('update:install', () => api.install());
20+
win.webContents.once('did-finish-load', () => api.check());
21+
}

src/preload/preload.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ contextBridge.exposeInMainWorld('devdeck', {
2323
pickFolder: () => ipcRenderer.invoke('settings:pickFolder'),
2424
openFolder: (path: string) => ipcRenderer.invoke('project:openFolder', path),
2525
openEditor: (path: string) => ipcRenderer.invoke('project:openEditor', path),
26+
onUpdate: (cb: (p: import('../shared/update').UpdatePayload) => void) =>
27+
ipcRenderer.on('devdeck:update', (_e, p) => cb(p as import('../shared/update').UpdatePayload)),
28+
downloadUpdate: () => ipcRenderer.invoke('update:download'),
29+
installUpdate: () => ipcRenderer.invoke('update:install'),
2630
windowControls: {
2731
minimize: () => ipcRenderer.invoke('win:minimize'),
2832
toggleMaximize: () => ipcRenderer.invoke('win:toggleMaximize'),

src/renderer/global.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ declare global {
2424
pickFolder(): Promise<string | null>;
2525
openFolder(path: string): Promise<void>;
2626
openEditor(path: string): Promise<void>;
27+
onUpdate(cb: (p: import('../shared/update').UpdatePayload) => void): void;
28+
downloadUpdate(): Promise<void>;
29+
installUpdate(): Promise<void>;
2730
windowControls: {
2831
minimize(): Promise<void>;
2932
toggleMaximize(): Promise<void>;

src/renderer/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<section id="view-next" class="view"></section>
5757
</main>
5858
</div>
59+
<div id="update-banner" class="hidden" role="status"></div>
5960
<div id="toast-host" aria-live="assertive"></div>
6061
<script src="./renderer.js"></script>
6162
</body>

src/renderer/locales/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,10 @@
6262
"proj.sort_activity": "Activity",
6363
"proj.sort_uncommitted": "Uncommitted",
6464
"proj.sort_name": "Name",
65-
"proj.sort_opened": "Last opened"
65+
"proj.sort_opened": "Last opened",
66+
"update.available": "Update available: v{version}",
67+
"update.download": "Download",
68+
"update.downloading": "Downloading update… {percent}%",
69+
"update.ready": "Update ready: v{version}",
70+
"update.restart": "Restart to update"
6671
}

src/renderer/locales/ja.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,10 @@
6262
"proj.sort_activity": "アクティビティ",
6363
"proj.sort_uncommitted": "未コミット",
6464
"proj.sort_name": "名前",
65-
"proj.sort_opened": "最終開覧"
65+
"proj.sort_opened": "最終開覧",
66+
"update.available": "アップデートあり: v{version}",
67+
"update.download": "ダウンロード",
68+
"update.downloading": "アップデートをダウンロード中… {percent}%",
69+
"update.ready": "アップデート準備完了: v{version}",
70+
"update.restart": "再起動して更新"
6671
}

src/renderer/locales/ko.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,10 @@
6262
"proj.sort_activity": "활동",
6363
"proj.sort_uncommitted": "미커밋",
6464
"proj.sort_name": "이름",
65-
"proj.sort_opened": "최근 열람"
65+
"proj.sort_opened": "최근 열람",
66+
"update.available": "업데이트 있음: v{version}",
67+
"update.download": "다운로드",
68+
"update.downloading": "업데이트 다운로드 중… {percent}%",
69+
"update.ready": "업데이트 준비됨: v{version}",
70+
"update.restart": "재시작하여 업데이트"
6671
}

0 commit comments

Comments
 (0)