Implement Electron app auto-updater (Phase 1 & 2)
Phase 1: Update Notifications & Download - Install electron-updater dependency - Add GitHub publish configuration to package.json Phase 2: Auto-Download & Install - Backend: Main process auto-updater with electron-updater - Frontend: AppUpdateNotification UI component - IPC: Full update workflow (check/download/install/events) Backend Implementation: - Created app-updater.ts with autoUpdater configuration - Auto-download enabled, checks every 4 hours - Events: update-available, update-downloaded, download-progress - Created app-update-handlers.ts with IPC handlers - Integrated into main/index.ts (production only) Frontend Implementation: - Created AppUpdateNotification.tsx with modal dialog - Shows version, release notes, download progress - Download workflow: Available → Downloading → Downloaded → Install - Created app-update-api.ts preload bindings - Integrated into App.tsx root level Configuration: - Added 8 IPC channels: APP_UPDATE_* operations & events - Created AppUpdateInfo, AppUpdateProgress, AppUpdate*Event types - GitHub release publishing: provider=github, repo=Auto-Claude Features: ✅ Automatic update checking (3s after launch, every 4 hours) ✅ Auto-download updates in background with progress bar ✅ User notification with version & release notes ✅ One-click install and restart ✅ Dismissible with "Remind Me Later" ✅ Error handling & visual feedback ✅ Production-only (disabled in dev mode) Non-technical users can now update the Electron app with one click! 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,312 @@
|
|||||||
|
# Auto Claude Update System Analysis
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
The app has **TWO separate update systems** for different components:
|
||||||
|
|
||||||
|
### 1. ✅ Auto Claude Framework Updates (WORKING)
|
||||||
|
|
||||||
|
**What it updates:** The Python framework source code (`auto-claude/` directory)
|
||||||
|
|
||||||
|
**How it works:**
|
||||||
|
- Checks GitHub Releases API for new versions
|
||||||
|
- Downloads release tarball
|
||||||
|
- Extracts and applies update to the bundled source
|
||||||
|
- Preserves user configuration files (.env, etc.)
|
||||||
|
|
||||||
|
**User Experience:**
|
||||||
|
- **Settings > Advanced > Updates** section
|
||||||
|
- Visual update checker with version display
|
||||||
|
- Release notes rendered in UI
|
||||||
|
- Progress bar during download
|
||||||
|
- One-click update button
|
||||||
|
- Works across all platforms (macOS, Windows, Linux)
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- `auto-claude-ui/src/main/auto-claude-updater.ts` - Main updater module
|
||||||
|
- `auto-claude-ui/src/main/updater/update-checker.ts` - Update checking
|
||||||
|
- `auto-claude-ui/src/main/updater/update-installer.ts` - Download & install
|
||||||
|
- `auto-claude-ui/src/main/ipc-handlers/autobuild-source-handlers.ts` - IPC handlers
|
||||||
|
- `auto-claude-ui/src/renderer/components/settings/AdvancedSettings.tsx` - UI
|
||||||
|
|
||||||
|
**Status:** ✅ **FULLY FUNCTIONAL** - Non-technical users can update the framework with one click!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. ❌ Electron App Updates (NOT IMPLEMENTED)
|
||||||
|
|
||||||
|
**What it updates:** The Electron application itself (Auto Claude UI)
|
||||||
|
|
||||||
|
**Current State:**
|
||||||
|
- ❌ No `electron-updater` dependency installed
|
||||||
|
- ❌ No auto-update configuration in electron-builder
|
||||||
|
- ❌ No update checking in main process
|
||||||
|
- ❌ No UI for app update notifications
|
||||||
|
- ❌ Users must manually download new releases from GitHub
|
||||||
|
|
||||||
|
**What users currently need to do:**
|
||||||
|
1. Go to GitHub Releases page
|
||||||
|
2. Download the appropriate installer (.dmg, .exe, .AppImage, etc.)
|
||||||
|
3. Run the installer
|
||||||
|
4. Manually replace the old app
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Needs to Be Implemented
|
||||||
|
|
||||||
|
To enable automatic Electron app updates for non-technical users, we need to add:
|
||||||
|
|
||||||
|
### 1. Install electron-updater
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install electron-updater
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure electron-builder for Publishing
|
||||||
|
|
||||||
|
Add to `package.json` build config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"build": {
|
||||||
|
"publish": [
|
||||||
|
{
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "AndyMik90",
|
||||||
|
"repo": "Auto-Claude"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Implement Auto-Update Logic in Main Process
|
||||||
|
|
||||||
|
Create `auto-claude-ui/src/main/app-updater.ts`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
import { app, BrowserWindow } from 'electron';
|
||||||
|
|
||||||
|
export function initializeAppUpdater(mainWindow: BrowserWindow) {
|
||||||
|
// Configure update checking
|
||||||
|
autoUpdater.autoDownload = false; // Let user decide
|
||||||
|
autoUpdater.autoInstallOnAppQuit = true;
|
||||||
|
|
||||||
|
// Check for updates on launch (after 3 seconds)
|
||||||
|
setTimeout(() => {
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// Check periodically (every 4 hours)
|
||||||
|
setInterval(() => {
|
||||||
|
autoUpdater.checkForUpdates();
|
||||||
|
}, 4 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
autoUpdater.on('update-available', (info) => {
|
||||||
|
mainWindow.webContents.send('app-update-available', {
|
||||||
|
version: info.version,
|
||||||
|
releaseNotes: info.releaseNotes,
|
||||||
|
releaseDate: info.releaseDate
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('update-downloaded', (info) => {
|
||||||
|
mainWindow.webContents.send('app-update-downloaded', {
|
||||||
|
version: info.version
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('error', (error) => {
|
||||||
|
console.error('App update error:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
autoUpdater.on('download-progress', (progress) => {
|
||||||
|
mainWindow.webContents.send('app-update-progress', {
|
||||||
|
percent: progress.percent,
|
||||||
|
transferred: progress.transferred,
|
||||||
|
total: progress.total
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC handlers
|
||||||
|
export function registerAppUpdateHandlers() {
|
||||||
|
ipcMain.handle('app-update-download', async () => {
|
||||||
|
await autoUpdater.downloadUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('app-update-install', () => {
|
||||||
|
autoUpdater.quitAndInstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('app-update-check', async () => {
|
||||||
|
return await autoUpdater.checkForUpdates();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Add UI Notification Component
|
||||||
|
|
||||||
|
Create an update banner or modal in the renderer that:
|
||||||
|
- Shows when app update is available
|
||||||
|
- Displays version and release notes
|
||||||
|
- Has "Download Update" button
|
||||||
|
- Shows download progress
|
||||||
|
- Has "Install and Restart" button after download
|
||||||
|
|
||||||
|
### 5. Update GitHub Release Workflow
|
||||||
|
|
||||||
|
Ensure GitHub releases are created with proper assets:
|
||||||
|
- macOS: `.dmg` and `.zip` files + `latest-mac.yml`
|
||||||
|
- Windows: `.exe` installer + `latest.yml`
|
||||||
|
- Linux: `.AppImage` and `.deb` + `latest-linux.yml`
|
||||||
|
|
||||||
|
The `latest-*.yml` files are auto-generated by electron-builder and contain update metadata.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Strategy
|
||||||
|
|
||||||
|
### Option A: Full Auto-Update (Recommended)
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- Best user experience
|
||||||
|
- Automatic background downloads
|
||||||
|
- One-click install
|
||||||
|
- Industry standard
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- Requires code signing certificates for production (macOS, Windows)
|
||||||
|
- Without signing, users get security warnings
|
||||||
|
|
||||||
|
### Option B: Update Notification Only
|
||||||
|
|
||||||
|
**Pros:**
|
||||||
|
- Simpler implementation
|
||||||
|
- No code signing required
|
||||||
|
- User downloads from GitHub (trusted source)
|
||||||
|
|
||||||
|
**Cons:**
|
||||||
|
- Users still need to manually download and install
|
||||||
|
- Less convenient than auto-update
|
||||||
|
|
||||||
|
**Implementation:**
|
||||||
|
```typescript
|
||||||
|
// Just notify, don't auto-download
|
||||||
|
autoUpdater.autoDownload = false;
|
||||||
|
|
||||||
|
autoUpdater.on('update-available', (info) => {
|
||||||
|
// Show notification with link to GitHub Releases
|
||||||
|
mainWindow.webContents.send('app-update-available', {
|
||||||
|
version: info.version,
|
||||||
|
downloadUrl: `https://github.com/AndyMik90/Auto-Claude/releases/tag/v${info.version}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Development vs Production
|
||||||
|
|
||||||
|
**Important:** `electron-updater` only works in **packaged apps**, not in development mode.
|
||||||
|
|
||||||
|
During development:
|
||||||
|
```typescript
|
||||||
|
if (app.isPackaged) {
|
||||||
|
initializeAppUpdater(mainWindow);
|
||||||
|
} else {
|
||||||
|
console.log('[Dev] Auto-updater disabled in development mode');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Signing Requirements
|
||||||
|
|
||||||
|
For production auto-updates without security warnings:
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
- Requires Apple Developer account ($99/year)
|
||||||
|
- Code signing certificate
|
||||||
|
- Notarization with Apple
|
||||||
|
|
||||||
|
### Windows
|
||||||
|
- Requires code signing certificate (~$200-400/year)
|
||||||
|
- Without: Windows SmartScreen warnings
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
- No code signing required
|
||||||
|
- Users may need to mark `.AppImage` as executable
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing Auto-Updates
|
||||||
|
|
||||||
|
1. **Local Testing:**
|
||||||
|
- Build and package: `npm run package`
|
||||||
|
- Create local update server or use GitHub Releases
|
||||||
|
- Test with different versions
|
||||||
|
|
||||||
|
2. **GitHub Releases Testing:**
|
||||||
|
- Create a draft release on GitHub
|
||||||
|
- Publish with version tag (e.g., `v2.4.0`)
|
||||||
|
- electron-builder automatically uploads assets
|
||||||
|
- Test with previous version installed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommended Next Steps
|
||||||
|
|
||||||
|
1. **Phase 1: Add Update Notification** (Quick win)
|
||||||
|
- Install `electron-updater`
|
||||||
|
- Add basic update checking
|
||||||
|
- Show notification with link to GitHub Releases
|
||||||
|
- No auto-download, users download manually
|
||||||
|
|
||||||
|
2. **Phase 2: Enable Auto-Download** (Better UX)
|
||||||
|
- Add download progress UI
|
||||||
|
- Enable auto-download of updates
|
||||||
|
- Add "Install and Restart" button
|
||||||
|
|
||||||
|
3. **Phase 3: Code Signing** (Production ready)
|
||||||
|
- Acquire code signing certificates
|
||||||
|
- Configure signing in electron-builder
|
||||||
|
- Notarize macOS builds
|
||||||
|
- Sign Windows builds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current Framework Update Flow (Already Working!)
|
||||||
|
|
||||||
|
For reference, here's how the existing Auto Claude framework updater works:
|
||||||
|
|
||||||
|
1. User opens **Settings > Advanced > Updates**
|
||||||
|
2. App checks GitHub Releases API
|
||||||
|
3. If update available, shows version + release notes
|
||||||
|
4. User clicks "Download Update"
|
||||||
|
5. Progress bar shows download status
|
||||||
|
6. Update is extracted and applied to bundled source
|
||||||
|
7. User configuration (.env) is preserved
|
||||||
|
8. Done - no restart needed!
|
||||||
|
|
||||||
|
**This same UX could be replicated for app updates!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
| Component | Status | User Experience |
|
||||||
|
|-----------|--------|-----------------|
|
||||||
|
| Auto Claude Framework | ✅ Working | One-click update in Settings |
|
||||||
|
| Electron App (UI) | ❌ Missing | Must download from GitHub manually |
|
||||||
|
|
||||||
|
**Recommendation:** Implement electron-updater with update notifications (Phase 1) as a quick win. This will enable non-technical users to update the app without using git or terminal commands.
|
||||||
|
|
||||||
|
**Estimated effort:**
|
||||||
|
- Phase 1 (Notifications): 2-4 hours
|
||||||
|
- Phase 2 (Auto-download): 2-3 hours
|
||||||
|
- Phase 3 (Code signing): Varies by platform
|
||||||
|
|
||||||
|
The existing framework updater code provides an excellent reference for the UI implementation!
|
||||||
Generated
+1541
-21
File diff suppressed because it is too large
Load Diff
@@ -55,6 +55,7 @@
|
|||||||
"chokidar": "^5.0.0",
|
"chokidar": "^5.0.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
"electron-updater": "^6.6.2",
|
||||||
"ioredis": "^5.8.2",
|
"ioredis": "^5.8.2",
|
||||||
"lucide-react": "^0.560.0",
|
"lucide-react": "^0.560.0",
|
||||||
"motion": "^12.23.26",
|
"motion": "^12.23.26",
|
||||||
@@ -114,6 +115,13 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"appId": "com.autoclaude.ui",
|
"appId": "com.autoclaude.ui",
|
||||||
"productName": "Auto Claude",
|
"productName": "Auto Claude",
|
||||||
|
"publish": [
|
||||||
|
{
|
||||||
|
"provider": "github",
|
||||||
|
"owner": "AndyMik90",
|
||||||
|
"repo": "Auto-Claude"
|
||||||
|
}
|
||||||
|
],
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "dist",
|
"output": "dist",
|
||||||
"buildResources": "resources"
|
"buildResources": "resources"
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
/**
|
||||||
|
* Electron App Auto-Updater
|
||||||
|
*
|
||||||
|
* Manages automatic updates for the packaged Electron application using electron-updater.
|
||||||
|
* Updates are published through GitHub Releases and automatically downloaded and installed.
|
||||||
|
*
|
||||||
|
* Update flow:
|
||||||
|
* 1. Check for updates 3 seconds after app launch
|
||||||
|
* 2. Download updates automatically when available
|
||||||
|
* 3. Notify user when update is downloaded
|
||||||
|
* 4. Install and restart when user confirms
|
||||||
|
*
|
||||||
|
* Events sent to renderer:
|
||||||
|
* - APP_UPDATE_AVAILABLE: New update available (with version info)
|
||||||
|
* - APP_UPDATE_DOWNLOADED: Update downloaded and ready to install
|
||||||
|
* - APP_UPDATE_PROGRESS: Download progress updates
|
||||||
|
* - APP_UPDATE_ERROR: Error during update process
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { autoUpdater } from 'electron-updater';
|
||||||
|
import type { BrowserWindow } from 'electron';
|
||||||
|
import { IPC_CHANNELS } from '../shared/constants';
|
||||||
|
import type { AppUpdateInfo } from '../shared/types';
|
||||||
|
|
||||||
|
// Configure electron-updater
|
||||||
|
autoUpdater.autoDownload = true; // Automatically download updates when available
|
||||||
|
autoUpdater.autoInstallOnAppQuit = true; // Automatically install on app quit
|
||||||
|
|
||||||
|
let mainWindow: BrowserWindow | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the app updater system
|
||||||
|
*
|
||||||
|
* Sets up event handlers and starts periodic update checks.
|
||||||
|
* Should only be called in production (app.isPackaged).
|
||||||
|
*
|
||||||
|
* @param window - The main BrowserWindow for sending update events
|
||||||
|
*/
|
||||||
|
export function initializeAppUpdater(window: BrowserWindow): void {
|
||||||
|
mainWindow = window;
|
||||||
|
|
||||||
|
// Log updater configuration
|
||||||
|
console.log('[app-updater] Initializing app auto-updater');
|
||||||
|
console.log('[app-updater] Current version:', autoUpdater.currentVersion.version);
|
||||||
|
console.log('[app-updater] Auto-download enabled:', autoUpdater.autoDownload);
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Event Handlers
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Update available - new version found
|
||||||
|
autoUpdater.on('update-available', (info) => {
|
||||||
|
console.log('[app-updater] Update available:', info.version);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_AVAILABLE, {
|
||||||
|
version: info.version,
|
||||||
|
releaseNotes: info.releaseNotes,
|
||||||
|
releaseDate: info.releaseDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update downloaded - ready to install
|
||||||
|
autoUpdater.on('update-downloaded', (info) => {
|
||||||
|
console.log('[app-updater] Update downloaded:', info.version);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_DOWNLOADED, {
|
||||||
|
version: info.version,
|
||||||
|
releaseNotes: info.releaseNotes,
|
||||||
|
releaseDate: info.releaseDate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download progress
|
||||||
|
autoUpdater.on('download-progress', (progress) => {
|
||||||
|
console.log(`[app-updater] Download progress: ${progress.percent.toFixed(2)}%`);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_PROGRESS, {
|
||||||
|
percent: progress.percent,
|
||||||
|
bytesPerSecond: progress.bytesPerSecond,
|
||||||
|
transferred: progress.transferred,
|
||||||
|
total: progress.total
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
autoUpdater.on('error', (error) => {
|
||||||
|
console.error('[app-updater] Update error:', error);
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send(IPC_CHANNELS.APP_UPDATE_ERROR, {
|
||||||
|
message: error.message,
|
||||||
|
stack: error.stack
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// No update available
|
||||||
|
autoUpdater.on('update-not-available', (info) => {
|
||||||
|
console.log('[app-updater] No updates available. Current version:', info.version);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Checking for updates
|
||||||
|
autoUpdater.on('checking-for-update', () => {
|
||||||
|
console.log('[app-updater] Checking for updates...');
|
||||||
|
});
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// Update Check Schedule
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
// Check for updates 3 seconds after launch
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('[app-updater] Performing initial update check');
|
||||||
|
autoUpdater.checkForUpdates().catch((error) => {
|
||||||
|
console.error('[app-updater] Initial update check failed:', error);
|
||||||
|
});
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
// Check for updates every 4 hours
|
||||||
|
const FOUR_HOURS = 4 * 60 * 60 * 1000;
|
||||||
|
setInterval(() => {
|
||||||
|
console.log('[app-updater] Performing periodic update check');
|
||||||
|
autoUpdater.checkForUpdates().catch((error) => {
|
||||||
|
console.error('[app-updater] Periodic update check failed:', error);
|
||||||
|
});
|
||||||
|
}, FOUR_HOURS);
|
||||||
|
|
||||||
|
console.log('[app-updater] Auto-updater initialized successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually check for updates
|
||||||
|
* Called from IPC handler when user requests manual check
|
||||||
|
*/
|
||||||
|
export async function checkForUpdates(): Promise<AppUpdateInfo | null> {
|
||||||
|
try {
|
||||||
|
console.log('[app-updater] Manual update check requested');
|
||||||
|
const result = await autoUpdater.checkForUpdates();
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAvailable = result.updateInfo.version !== autoUpdater.currentVersion.version;
|
||||||
|
|
||||||
|
if (!updateAvailable) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: result.updateInfo.version,
|
||||||
|
releaseNotes: result.updateInfo.releaseNotes as string | undefined,
|
||||||
|
releaseDate: result.updateInfo.releaseDate
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[app-updater] Manual update check failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually download update
|
||||||
|
* Called from IPC handler when user requests manual download
|
||||||
|
*/
|
||||||
|
export async function downloadUpdate(): Promise<void> {
|
||||||
|
try {
|
||||||
|
console.log('[app-updater] Manual update download requested');
|
||||||
|
await autoUpdater.downloadUpdate();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[app-updater] Manual update download failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quit and install update
|
||||||
|
* Called from IPC handler when user confirms installation
|
||||||
|
*/
|
||||||
|
export function quitAndInstall(): void {
|
||||||
|
console.log('[app-updater] Quitting and installing update');
|
||||||
|
autoUpdater.quitAndInstall(false, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get current app version
|
||||||
|
*/
|
||||||
|
export function getCurrentVersion(): string {
|
||||||
|
return autoUpdater.currentVersion.version;
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { TerminalManager } from './terminal-manager';
|
|||||||
import { pythonEnvManager } from './python-env-manager';
|
import { pythonEnvManager } from './python-env-manager';
|
||||||
import { getUsageMonitor } from './claude-profile/usage-monitor';
|
import { getUsageMonitor } from './claude-profile/usage-monitor';
|
||||||
import { initializeUsageMonitorForwarding } from './ipc-handlers/terminal-handlers';
|
import { initializeUsageMonitorForwarding } from './ipc-handlers/terminal-handlers';
|
||||||
|
import { initializeAppUpdater } from './app-updater';
|
||||||
|
|
||||||
// Get icon path based on platform
|
// Get icon path based on platform
|
||||||
function getIconPath(): string {
|
function getIconPath(): string {
|
||||||
@@ -137,6 +138,14 @@ app.whenReady().then(() => {
|
|||||||
const usageMonitor = getUsageMonitor();
|
const usageMonitor = getUsageMonitor();
|
||||||
usageMonitor.start();
|
usageMonitor.start();
|
||||||
console.log('[main] Usage monitor initialized and started');
|
console.log('[main] Usage monitor initialized and started');
|
||||||
|
|
||||||
|
// Initialize app auto-updater (only in production)
|
||||||
|
if (app.isPackaged) {
|
||||||
|
initializeAppUpdater(mainWindow);
|
||||||
|
console.log('[main] App auto-updater initialized');
|
||||||
|
} else {
|
||||||
|
console.log('[main] App auto-updater disabled in development mode');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// macOS: re-create window when dock icon is clicked
|
// macOS: re-create window when dock icon is clicked
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
/**
|
||||||
|
* App Update IPC Handlers
|
||||||
|
*
|
||||||
|
* Handles IPC communication for Electron app auto-updates.
|
||||||
|
* Provides manual controls for checking, downloading, and installing updates.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ipcMain } from 'electron';
|
||||||
|
import { IPC_CHANNELS } from '../../shared/constants';
|
||||||
|
import type { IPCResult, AppUpdateInfo } from '../../shared/types';
|
||||||
|
import {
|
||||||
|
checkForUpdates,
|
||||||
|
downloadUpdate,
|
||||||
|
quitAndInstall,
|
||||||
|
getCurrentVersion
|
||||||
|
} from '../app-updater';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register all app-update-related IPC handlers
|
||||||
|
*/
|
||||||
|
export function registerAppUpdateHandlers(): void {
|
||||||
|
console.log('[IPC] Registering app update handlers');
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// App Update Operations
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP_UPDATE_CHECK: Manually check for updates
|
||||||
|
* Returns update availability and version information
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.APP_UPDATE_CHECK,
|
||||||
|
async (): Promise<IPCResult<AppUpdateInfo | null>> => {
|
||||||
|
try {
|
||||||
|
const result = await checkForUpdates();
|
||||||
|
return { success: true, data: result };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[app-update-handlers] Check for updates failed:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to check for updates'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP_UPDATE_DOWNLOAD: Manually download update
|
||||||
|
* Triggers download of available update
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.APP_UPDATE_DOWNLOAD,
|
||||||
|
async (): Promise<IPCResult> => {
|
||||||
|
try {
|
||||||
|
await downloadUpdate();
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[app-update-handlers] Download update failed:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to download update'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP_UPDATE_INSTALL: Quit and install update
|
||||||
|
* Quits the app and installs the downloaded update
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.APP_UPDATE_INSTALL,
|
||||||
|
async (): Promise<IPCResult> => {
|
||||||
|
try {
|
||||||
|
quitAndInstall();
|
||||||
|
return { success: true };
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[app-update-handlers] Install update failed:', error);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Failed to install update'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* APP_UPDATE_GET_VERSION: Get current app version
|
||||||
|
* Returns the current application version
|
||||||
|
*/
|
||||||
|
ipcMain.handle(
|
||||||
|
IPC_CHANNELS.APP_UPDATE_GET_VERSION,
|
||||||
|
async (): Promise<string> => {
|
||||||
|
try {
|
||||||
|
const version = getCurrentVersion();
|
||||||
|
return version;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[app-update-handlers] Get version failed:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log('[IPC] App update handlers registered successfully');
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import { registerIdeationHandlers } from './ideation-handlers';
|
|||||||
import { registerChangelogHandlers } from './changelog-handlers';
|
import { registerChangelogHandlers } from './changelog-handlers';
|
||||||
import { registerInsightsHandlers } from './insights-handlers';
|
import { registerInsightsHandlers } from './insights-handlers';
|
||||||
import { registerDockerHandlers } from './docker-handlers';
|
import { registerDockerHandlers } from './docker-handlers';
|
||||||
|
import { registerAppUpdateHandlers } from './app-update-handlers';
|
||||||
import { notificationService } from '../notification-service';
|
import { notificationService } from '../notification-service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,6 +95,9 @@ export function setupIpcHandlers(
|
|||||||
// Docker & infrastructure handlers (for Graphiti/FalkorDB)
|
// Docker & infrastructure handlers (for Graphiti/FalkorDB)
|
||||||
registerDockerHandlers();
|
registerDockerHandlers();
|
||||||
|
|
||||||
|
// App auto-update handlers
|
||||||
|
registerAppUpdateHandlers();
|
||||||
|
|
||||||
console.log('[IPC] All handler modules registered successfully');
|
console.log('[IPC] All handler modules registered successfully');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,5 +118,6 @@ export {
|
|||||||
registerIdeationHandlers,
|
registerIdeationHandlers,
|
||||||
registerChangelogHandlers,
|
registerChangelogHandlers,
|
||||||
registerInsightsHandlers,
|
registerInsightsHandlers,
|
||||||
registerDockerHandlers
|
registerDockerHandlers,
|
||||||
|
registerAppUpdateHandlers
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { IPC_CHANNELS } from '../../shared/constants';
|
||||||
|
import type {
|
||||||
|
AppUpdateInfo,
|
||||||
|
AppUpdateProgress,
|
||||||
|
AppUpdateAvailableEvent,
|
||||||
|
AppUpdateDownloadedEvent,
|
||||||
|
IPCResult
|
||||||
|
} from '../../shared/types';
|
||||||
|
import { createIpcListener, invokeIpc, IpcListenerCleanup } from './modules/ipc-utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App Auto-Update API operations
|
||||||
|
* Handles Electron app updates using electron-updater
|
||||||
|
*/
|
||||||
|
export interface AppUpdateAPI {
|
||||||
|
// Operations
|
||||||
|
checkAppUpdate: () => Promise<IPCResult<AppUpdateInfo | null>>;
|
||||||
|
downloadAppUpdate: () => Promise<IPCResult>;
|
||||||
|
installAppUpdate: () => void;
|
||||||
|
getAppVersion: () => Promise<string>;
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
onAppUpdateAvailable: (
|
||||||
|
callback: (info: AppUpdateAvailableEvent) => void
|
||||||
|
) => IpcListenerCleanup;
|
||||||
|
onAppUpdateDownloaded: (
|
||||||
|
callback: (info: AppUpdateDownloadedEvent) => void
|
||||||
|
) => IpcListenerCleanup;
|
||||||
|
onAppUpdateProgress: (
|
||||||
|
callback: (progress: AppUpdateProgress) => void
|
||||||
|
) => IpcListenerCleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the App Auto-Update API implementation
|
||||||
|
*/
|
||||||
|
export const createAppUpdateAPI = (): AppUpdateAPI => ({
|
||||||
|
// Operations
|
||||||
|
checkAppUpdate: (): Promise<IPCResult<AppUpdateInfo | null>> =>
|
||||||
|
invokeIpc(IPC_CHANNELS.APP_UPDATE_CHECK),
|
||||||
|
|
||||||
|
downloadAppUpdate: (): Promise<IPCResult> =>
|
||||||
|
invokeIpc(IPC_CHANNELS.APP_UPDATE_DOWNLOAD),
|
||||||
|
|
||||||
|
installAppUpdate: (): void => {
|
||||||
|
invokeIpc(IPC_CHANNELS.APP_UPDATE_INSTALL);
|
||||||
|
},
|
||||||
|
|
||||||
|
getAppVersion: (): Promise<string> =>
|
||||||
|
invokeIpc(IPC_CHANNELS.APP_UPDATE_GET_VERSION),
|
||||||
|
|
||||||
|
// Event Listeners
|
||||||
|
onAppUpdateAvailable: (
|
||||||
|
callback: (info: AppUpdateAvailableEvent) => void
|
||||||
|
): IpcListenerCleanup =>
|
||||||
|
createIpcListener(IPC_CHANNELS.APP_UPDATE_AVAILABLE, callback),
|
||||||
|
|
||||||
|
onAppUpdateDownloaded: (
|
||||||
|
callback: (info: AppUpdateDownloadedEvent) => void
|
||||||
|
): IpcListenerCleanup =>
|
||||||
|
createIpcListener(IPC_CHANNELS.APP_UPDATE_DOWNLOADED, callback),
|
||||||
|
|
||||||
|
onAppUpdateProgress: (
|
||||||
|
callback: (progress: AppUpdateProgress) => void
|
||||||
|
): IpcListenerCleanup =>
|
||||||
|
createIpcListener(IPC_CHANNELS.APP_UPDATE_PROGRESS, callback)
|
||||||
|
});
|
||||||
@@ -5,6 +5,7 @@ import { SettingsAPI, createSettingsAPI } from './settings-api';
|
|||||||
import { FileAPI, createFileAPI } from './file-api';
|
import { FileAPI, createFileAPI } from './file-api';
|
||||||
import { AgentAPI, createAgentAPI } from './agent-api';
|
import { AgentAPI, createAgentAPI } from './agent-api';
|
||||||
import { IdeationAPI, createIdeationAPI } from './modules/ideation-api';
|
import { IdeationAPI, createIdeationAPI } from './modules/ideation-api';
|
||||||
|
import { AppUpdateAPI, createAppUpdateAPI } from './app-update-api';
|
||||||
|
|
||||||
export interface ElectronAPI extends
|
export interface ElectronAPI extends
|
||||||
ProjectAPI,
|
ProjectAPI,
|
||||||
@@ -13,7 +14,8 @@ export interface ElectronAPI extends
|
|||||||
SettingsAPI,
|
SettingsAPI,
|
||||||
FileAPI,
|
FileAPI,
|
||||||
AgentAPI,
|
AgentAPI,
|
||||||
IdeationAPI {}
|
IdeationAPI,
|
||||||
|
AppUpdateAPI {}
|
||||||
|
|
||||||
export const createElectronAPI = (): ElectronAPI => ({
|
export const createElectronAPI = (): ElectronAPI => ({
|
||||||
...createProjectAPI(),
|
...createProjectAPI(),
|
||||||
@@ -22,7 +24,8 @@ export const createElectronAPI = (): ElectronAPI => ({
|
|||||||
...createSettingsAPI(),
|
...createSettingsAPI(),
|
||||||
...createFileAPI(),
|
...createFileAPI(),
|
||||||
...createAgentAPI(),
|
...createAgentAPI(),
|
||||||
...createIdeationAPI()
|
...createIdeationAPI(),
|
||||||
|
...createAppUpdateAPI()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Export individual API creators for potential use in tests or specialized contexts
|
// Export individual API creators for potential use in tests or specialized contexts
|
||||||
@@ -33,7 +36,8 @@ export {
|
|||||||
createSettingsAPI,
|
createSettingsAPI,
|
||||||
createFileAPI,
|
createFileAPI,
|
||||||
createAgentAPI,
|
createAgentAPI,
|
||||||
createIdeationAPI
|
createIdeationAPI,
|
||||||
|
createAppUpdateAPI
|
||||||
};
|
};
|
||||||
|
|
||||||
export type {
|
export type {
|
||||||
@@ -43,5 +47,6 @@ export type {
|
|||||||
SettingsAPI,
|
SettingsAPI,
|
||||||
FileAPI,
|
FileAPI,
|
||||||
AgentAPI,
|
AgentAPI,
|
||||||
IdeationAPI
|
IdeationAPI,
|
||||||
|
AppUpdateAPI
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import { WelcomeScreen } from './components/WelcomeScreen';
|
|||||||
import { RateLimitModal } from './components/RateLimitModal';
|
import { RateLimitModal } from './components/RateLimitModal';
|
||||||
import { SDKRateLimitModal } from './components/SDKRateLimitModal';
|
import { SDKRateLimitModal } from './components/SDKRateLimitModal';
|
||||||
import { OnboardingWizard } from './components/onboarding';
|
import { OnboardingWizard } from './components/onboarding';
|
||||||
|
import { AppUpdateNotification } from './components/AppUpdateNotification';
|
||||||
import { UsageIndicator } from './components/UsageIndicator';
|
import { UsageIndicator } from './components/UsageIndicator';
|
||||||
import { ProactiveSwapListener } from './components/ProactiveSwapListener';
|
import { ProactiveSwapListener } from './components/ProactiveSwapListener';
|
||||||
import { useProjectStore, loadProjects, addProject, initializeProject } from './stores/project-store';
|
import { useProjectStore, loadProjects, addProject, initializeProject } from './stores/project-store';
|
||||||
@@ -479,6 +480,9 @@ export function App() {
|
|||||||
setIsSettingsDialogOpen(true);
|
setIsSettingsDialogOpen(true);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* App Update Notification - shows when new app version is available */}
|
||||||
|
<AppUpdateNotification />
|
||||||
</div>
|
</div>
|
||||||
</TooltipProvider>
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,252 @@
|
|||||||
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
Download,
|
||||||
|
RefreshCw,
|
||||||
|
CheckCircle2,
|
||||||
|
AlertCircle,
|
||||||
|
ExternalLink
|
||||||
|
} from 'lucide-react';
|
||||||
|
import { Button } from './ui/button';
|
||||||
|
import { Progress } from './ui/progress';
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle
|
||||||
|
} from './ui/dialog';
|
||||||
|
import type {
|
||||||
|
AppUpdateAvailableEvent,
|
||||||
|
AppUpdateDownloadedEvent,
|
||||||
|
AppUpdateProgress
|
||||||
|
} from '../../shared/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple markdown renderer for release notes
|
||||||
|
* Handles: headers, bold, lists, line breaks
|
||||||
|
*/
|
||||||
|
function ReleaseNotesRenderer({ markdown }: { markdown: string }) {
|
||||||
|
const html = useMemo(() => {
|
||||||
|
const result = markdown
|
||||||
|
// Escape HTML
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
// Headers (### Header -> <h3>)
|
||||||
|
.replace(/^### (.+)$/gm, '<h4 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h4>')
|
||||||
|
.replace(/^## (.+)$/gm, '<h3 class="text-sm font-semibold text-foreground mt-3 mb-1.5 first:mt-0">$1</h3>')
|
||||||
|
// Bold (**text** -> <strong>)
|
||||||
|
.replace(/\*\*([^*]+)\*\*/g, '<strong class="text-foreground font-medium">$1</strong>')
|
||||||
|
// Inline code (`code` -> <code>)
|
||||||
|
.replace(/`([^`]+)`/g, '<code class="px-1 py-0.5 bg-muted rounded text-xs">$1</code>')
|
||||||
|
// List items (- item -> <li>)
|
||||||
|
.replace(/^- (.+)$/gm, '<li class="ml-4 text-muted-foreground before:content-[\'•\'] before:mr-2 before:text-muted-foreground/60">$1</li>')
|
||||||
|
// Wrap consecutive list items
|
||||||
|
.replace(/(<li[^>]*>.*?<\/li>\n?)+/g, '<ul class="space-y-1 my-2">$&</ul>')
|
||||||
|
// Line breaks for remaining lines
|
||||||
|
.replace(/\n\n/g, '<div class="h-2"></div>')
|
||||||
|
.replace(/\n/g, '<br/>');
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [markdown]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="text-sm text-muted-foreground leading-relaxed"
|
||||||
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* App Update Notification Dialog
|
||||||
|
* Shows when a new app version is available and handles download/install workflow
|
||||||
|
*/
|
||||||
|
export function AppUpdateNotification() {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [updateInfo, setUpdateInfo] = useState<AppUpdateAvailableEvent | null>(null);
|
||||||
|
const [downloadProgress, setDownloadProgress] = useState<AppUpdateProgress | null>(null);
|
||||||
|
const [isDownloading, setIsDownloading] = useState(false);
|
||||||
|
const [isDownloaded, setIsDownloaded] = useState(false);
|
||||||
|
const [downloadError, setDownloadError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Listen for update available event
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanup = window.electronAPI.onAppUpdateAvailable((info) => {
|
||||||
|
setUpdateInfo(info);
|
||||||
|
setIsOpen(true);
|
||||||
|
setIsDownloading(false);
|
||||||
|
setIsDownloaded(false);
|
||||||
|
setDownloadProgress(null);
|
||||||
|
setDownloadError(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Listen for update downloaded event
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanup = window.electronAPI.onAppUpdateDownloaded((info) => {
|
||||||
|
setIsDownloading(false);
|
||||||
|
setIsDownloaded(true);
|
||||||
|
setDownloadProgress(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Listen for download progress
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanup = window.electronAPI.onAppUpdateProgress((progress) => {
|
||||||
|
setDownloadProgress(progress);
|
||||||
|
});
|
||||||
|
|
||||||
|
return cleanup;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDownload = async () => {
|
||||||
|
setIsDownloading(true);
|
||||||
|
setDownloadError(null);
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.downloadAppUpdate();
|
||||||
|
if (!result.success) {
|
||||||
|
setDownloadError(result.error || 'Failed to download update');
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download app update:', error);
|
||||||
|
setDownloadError('Failed to download update');
|
||||||
|
setIsDownloading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInstall = () => {
|
||||||
|
window.electronAPI.installAppUpdate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDismiss = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!updateInfo) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||||
|
<DialogContent className="max-w-2xl">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle className="flex items-center gap-2">
|
||||||
|
<Download className="h-5 w-5" />
|
||||||
|
App Update Available
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
A new version of Auto Claude is ready to download
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
{/* Version Info */}
|
||||||
|
<div className="rounded-lg border border-border bg-muted/50 p-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
|
||||||
|
New Version
|
||||||
|
</p>
|
||||||
|
<p className="text-base font-medium text-foreground">
|
||||||
|
{updateInfo.version}
|
||||||
|
</p>
|
||||||
|
{updateInfo.releaseDate && (
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Released {new Date(updateInfo.releaseDate).toLocaleDateString()}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{isDownloaded ? (
|
||||||
|
<CheckCircle2 className="h-6 w-6 text-success" />
|
||||||
|
) : isDownloading ? (
|
||||||
|
<RefreshCw className="h-6 w-6 animate-spin text-info" />
|
||||||
|
) : (
|
||||||
|
<Download className="h-6 w-6 text-info" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Release Notes */}
|
||||||
|
{updateInfo.releaseNotes && (
|
||||||
|
<div className="bg-background rounded-lg p-4 max-h-64 overflow-y-auto border border-border/50">
|
||||||
|
<ReleaseNotesRenderer markdown={updateInfo.releaseNotes} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Download Progress */}
|
||||||
|
{isDownloading && downloadProgress && (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="flex items-center justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Downloading...</span>
|
||||||
|
<span className="text-foreground font-medium">
|
||||||
|
{Math.round(downloadProgress.percent)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={downloadProgress.percent} className="h-2" />
|
||||||
|
<p className="text-xs text-muted-foreground text-right">
|
||||||
|
{(downloadProgress.transferred / 1024 / 1024).toFixed(2)} MB / {(downloadProgress.total / 1024 / 1024).toFixed(2)} MB
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Download Error */}
|
||||||
|
{downloadError && (
|
||||||
|
<div className="flex items-center gap-3 text-sm text-destructive bg-destructive/10 border border-destructive/30 rounded-lg p-3">
|
||||||
|
<AlertCircle className="h-5 w-5 shrink-0" />
|
||||||
|
<span>{downloadError}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Downloaded Success */}
|
||||||
|
{isDownloaded && (
|
||||||
|
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
|
||||||
|
<CheckCircle2 className="h-5 w-5 shrink-0" />
|
||||||
|
<span>Update downloaded successfully! Click Install to restart and apply the update.</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DialogFooter className="flex flex-col sm:flex-row gap-3">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={handleDismiss}
|
||||||
|
disabled={isDownloading}
|
||||||
|
>
|
||||||
|
{isDownloaded ? 'Install Later' : 'Remind Me Later'}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isDownloaded ? (
|
||||||
|
<Button onClick={handleInstall}>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
|
Install and Restart
|
||||||
|
</Button>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
onClick={handleDownload}
|
||||||
|
disabled={isDownloading}
|
||||||
|
>
|
||||||
|
{isDownloading ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
Downloading...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Download className="mr-2 h-4 w-4" />
|
||||||
|
Download Update
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -257,5 +257,17 @@ export const IPC_CHANNELS = {
|
|||||||
// Git operations
|
// Git operations
|
||||||
GIT_GET_BRANCHES: 'git:getBranches',
|
GIT_GET_BRANCHES: 'git:getBranches',
|
||||||
GIT_GET_CURRENT_BRANCH: 'git:getCurrentBranch',
|
GIT_GET_CURRENT_BRANCH: 'git:getCurrentBranch',
|
||||||
GIT_DETECT_MAIN_BRANCH: 'git:detectMainBranch'
|
GIT_DETECT_MAIN_BRANCH: 'git:detectMainBranch',
|
||||||
|
|
||||||
|
// App auto-update operations
|
||||||
|
APP_UPDATE_CHECK: 'app-update:check',
|
||||||
|
APP_UPDATE_DOWNLOAD: 'app-update:download',
|
||||||
|
APP_UPDATE_INSTALL: 'app-update:install',
|
||||||
|
APP_UPDATE_GET_VERSION: 'app-update:get-version',
|
||||||
|
|
||||||
|
// App auto-update events (main -> renderer)
|
||||||
|
APP_UPDATE_AVAILABLE: 'app-update:available',
|
||||||
|
APP_UPDATE_DOWNLOADED: 'app-update:downloaded',
|
||||||
|
APP_UPDATE_PROGRESS: 'app-update:progress',
|
||||||
|
APP_UPDATE_ERROR: 'app-update:error'
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* Types for Electron app auto-update functionality
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AppUpdateInfo {
|
||||||
|
version: string;
|
||||||
|
releaseNotes?: string;
|
||||||
|
releaseDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppUpdateProgress {
|
||||||
|
percent: number;
|
||||||
|
transferred: number;
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppUpdateAvailableEvent {
|
||||||
|
version: string;
|
||||||
|
releaseNotes?: string;
|
||||||
|
releaseDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AppUpdateDownloadedEvent {
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ export * from './changelog';
|
|||||||
export * from './insights';
|
export * from './insights';
|
||||||
export * from './roadmap';
|
export * from './roadmap';
|
||||||
export * from './integrations';
|
export * from './integrations';
|
||||||
|
export * from './glossary';
|
||||||
|
export * from './status';
|
||||||
|
export * from './app-update';
|
||||||
|
|
||||||
// IPC types (must be last to use types from other modules)
|
// IPC types (must be last to use types from other modules)
|
||||||
export * from './ipc';
|
export * from './ipc';
|
||||||
|
|||||||
Reference in New Issue
Block a user