chore: git publish + auto update handling (#31)
* chore: git publish + auto update handling
This commit is contained in:
parent
d05bf80f10
commit
aecabbeb4a
12 changed files with 957 additions and 3 deletions
|
|
@ -6,6 +6,7 @@ import { MakerRpm } from '@electron-forge/maker-rpm';
|
|||
import { VitePlugin } from '@electron-forge/plugin-vite';
|
||||
import { FusesPlugin } from '@electron-forge/plugin-fuses';
|
||||
import { FuseV1Options, FuseVersion } from '@electron/fuses';
|
||||
import { PublisherGithub } from '@electron-forge/publisher-github';
|
||||
import { readdirSync, rmdirSync, statSync, existsSync, mkdirSync, cpSync } from 'node:fs';
|
||||
import { join, normalize } from 'node:path';
|
||||
// Use flora-colossus for finding all dependencies of EXTERNAL_DEPENDENCIES
|
||||
|
|
@ -192,6 +193,19 @@ const config: ForgeConfig = {
|
|||
NSMicrophoneUsageDescription:
|
||||
'This app needs access to your microphone to record audio for transcription.',
|
||||
},
|
||||
// Code signing configuration for macOS (configure when ready to sign)
|
||||
// osxSign: {
|
||||
// identity: process.env.APPLE_SIGNING_IDENTITY,
|
||||
// 'hardened-runtime': true,
|
||||
// entitlements: './entitlements.plist',
|
||||
// 'entitlements-inherit': './entitlements.plist',
|
||||
// },
|
||||
// Notarization for macOS (configure when ready for distribution)
|
||||
// osxNotarize: {
|
||||
// appleId: process.env.APPLE_ID,
|
||||
// appleIdPassword: process.env.APPLE_APP_PASSWORD,
|
||||
// teamId: process.env.APPLE_TEAM_ID,
|
||||
// },
|
||||
//! issues with monorepo setup and module resolutions
|
||||
//! when forge walks paths via flora-colossus
|
||||
prune: false,
|
||||
|
|
@ -305,6 +319,16 @@ const config: ForgeConfig = {
|
|||
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
||||
}),
|
||||
],
|
||||
publishers: [
|
||||
new PublisherGithub({
|
||||
repository: {
|
||||
owner: 'amicalhq',
|
||||
name: 'amical',
|
||||
},
|
||||
prerelease: true,
|
||||
draft: true, // Create draft releases first for review
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@amical/desktop",
|
||||
"version": "1.0.0",
|
||||
"version": "0.0.1",
|
||||
"description": "Amical Desktop app",
|
||||
"main": ".vite/build/main.js",
|
||||
"productName": "Amical",
|
||||
|
|
@ -31,6 +31,7 @@
|
|||
"@electron-forge/plugin-auto-unpack-natives": "^7.8.1",
|
||||
"@electron-forge/plugin-fuses": "^7.8.1",
|
||||
"@electron-forge/plugin-vite": "^7.8.1",
|
||||
"@electron-forge/publisher-github": "^7.8.1",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@rollup/plugin-commonjs": "^28.0.6",
|
||||
"@tailwindcss/vite": "^4.1.6",
|
||||
|
|
@ -105,6 +106,7 @@
|
|||
"electron-log": "^5.4.0",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-trpc-experimental": "1.0.0-alpha.1",
|
||||
"electron-updater": "^6.6.2",
|
||||
"embla-carousel-react": "^8.6.0",
|
||||
"framer-motion": "^12.10.5",
|
||||
"input-otp": "^1.4.2",
|
||||
|
|
|
|||
238
apps/desktop/src/components/update-dialog.tsx
Normal file
238
apps/desktop/src/components/update-dialog.tsx
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from './ui/alert-dialog';
|
||||
import { Progress } from './ui/progress';
|
||||
import { Button } from './ui/button';
|
||||
import { Download, RefreshCw, CheckCircle } from 'lucide-react';
|
||||
import { api } from '@/trpc/react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface UpdateDialogProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
updateInfo?: {
|
||||
version: string;
|
||||
releaseNotes?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function UpdateDialog({ isOpen, onClose, updateInfo }: UpdateDialogProps) {
|
||||
const [isDownloading, setIsDownloading] = useState(false);
|
||||
const [downloadProgress, setDownloadProgress] = useState(0);
|
||||
|
||||
// tRPC queries for update status
|
||||
const isCheckingQuery = api.updater.isCheckingForUpdate.useQuery(undefined, {
|
||||
enabled: isOpen,
|
||||
refetchInterval: isOpen ? 1000 : false, // Poll every second when dialog is open
|
||||
});
|
||||
const isUpdateAvailableQuery = api.updater.isUpdateAvailable.useQuery(undefined, {
|
||||
enabled: isOpen,
|
||||
refetchInterval: isOpen ? 1000 : false,
|
||||
});
|
||||
|
||||
const utils = api.useUtils();
|
||||
|
||||
// tRPC mutations
|
||||
const checkForUpdatesMutation = api.updater.checkForUpdates.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success('Update check completed');
|
||||
utils.updater.isUpdateAvailable.invalidate();
|
||||
utils.updater.isCheckingForUpdate.invalidate();
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Error checking for updates:', error);
|
||||
toast.error('Failed to check for updates');
|
||||
}
|
||||
});
|
||||
|
||||
const downloadUpdateMutation = api.updater.downloadUpdate.useMutation({
|
||||
onSuccess: () => {
|
||||
toast.success('Update download started');
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Error downloading update:', error);
|
||||
toast.error('Failed to download update');
|
||||
setIsDownloading(false);
|
||||
}
|
||||
});
|
||||
|
||||
const quitAndInstallMutation = api.updater.quitAndInstall.useMutation({
|
||||
onError: (error) => {
|
||||
console.error('Error installing update:', error);
|
||||
toast.error('Failed to install update');
|
||||
}
|
||||
});
|
||||
|
||||
// Get status from queries
|
||||
const isCheckingForUpdates = isCheckingQuery.data || false;
|
||||
const updateAvailable = isUpdateAvailableQuery.data || false;
|
||||
|
||||
// Subscribe to download progress via tRPC
|
||||
api.updater.onDownloadProgress.useSubscription(undefined, {
|
||||
enabled: isOpen && isDownloading,
|
||||
onData: (progress) => {
|
||||
setDownloadProgress(Math.round(progress.percent || 0));
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error('Download progress subscription error:', error);
|
||||
}
|
||||
});
|
||||
|
||||
const handleCheckForUpdates = async () => {
|
||||
checkForUpdatesMutation.mutate({ userInitiated: true });
|
||||
};
|
||||
|
||||
const handleDownloadUpdate = async () => {
|
||||
setIsDownloading(true);
|
||||
setDownloadProgress(0);
|
||||
downloadUpdateMutation.mutate();
|
||||
};
|
||||
|
||||
const handleInstallUpdate = async () => {
|
||||
quitAndInstallMutation.mutate();
|
||||
};
|
||||
|
||||
if (!updateAvailable && !isCheckingForUpdates && !isDownloading) {
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={onClose}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<RefreshCw className="h-5 w-5" />
|
||||
Check for Updates
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Click below to check for the latest version of Amical.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={onClose}>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleCheckForUpdates}>
|
||||
Check for Updates
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
||||
if (isCheckingForUpdates) {
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={onClose}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<RefreshCw className="h-5 w-5 animate-spin" />
|
||||
Checking for Updates...
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
Please wait while we check for the latest version.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={onClose}>Cancel</AlertDialogCancel>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDownloading) {
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={() => {}}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5" />
|
||||
Downloading Update...
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{updateInfo?.version && (
|
||||
<>Downloading version {updateInfo.version}. Please wait...</>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<div className="py-4">
|
||||
<Progress value={downloadProgress} className="w-full" />
|
||||
<p className="text-sm text-muted-foreground mt-2 text-center">
|
||||
{downloadProgress}% complete
|
||||
</p>
|
||||
</div>
|
||||
<AlertDialogFooter>
|
||||
<Button variant="outline" disabled>
|
||||
Downloading...
|
||||
</Button>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
||||
if (downloadProgress === 100 && !isDownloading) {
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={() => {}}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<CheckCircle className="h-5 w-5 text-green-500" />
|
||||
Update Ready
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{updateInfo?.version && (
|
||||
<>
|
||||
Version {updateInfo.version} has been downloaded and is ready to install.
|
||||
The app will restart to complete the installation.
|
||||
</>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={onClose}>Install Later</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleInstallUpdate}>
|
||||
Restart & Install
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<AlertDialog open={isOpen} onOpenChange={onClose}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="flex items-center gap-2">
|
||||
<Download className="h-5 w-5" />
|
||||
Update Available
|
||||
</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
{updateInfo?.version && (
|
||||
<>
|
||||
A new version ({updateInfo.version}) is available for download.
|
||||
{updateInfo.releaseNotes && (
|
||||
<div className="mt-2 p-2 bg-muted rounded text-sm">
|
||||
{updateInfo.releaseNotes}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel onClick={onClose}>Later</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDownloadUpdate}>
|
||||
Download Now
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -111,6 +111,7 @@ export const logger = {
|
|||
swift: createLoggerForScope('swift'),
|
||||
ui: createLoggerForScope('ui'),
|
||||
db: createLoggerForScope('db'),
|
||||
updater: createLoggerForScope('updater'),
|
||||
};
|
||||
|
||||
// Log startup information
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import { ContextualTranscriptionManager } from '../modules/transcription/context
|
|||
import { SettingsService } from '../modules/settings';
|
||||
import { createIPCHandler } from 'electron-trpc-experimental/main';
|
||||
import { router } from '../trpc/router';
|
||||
import { AutoUpdaterService } from './services/auto-updater';
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (started) {
|
||||
|
|
@ -52,6 +53,7 @@ let activeSpaceChangeSubscriptionId: number | null = null; // For display change
|
|||
// New chunk-based transcription variables
|
||||
let contextualTranscriptionManager: ContextualTranscriptionManager | null = null;
|
||||
const activeTranscriptionSessions: Map<string, TranscriptionSession> = new Map();
|
||||
let autoUpdaterService: AutoUpdaterService | null = null;
|
||||
|
||||
// Store is imported from '../lib/store' and is database-backed
|
||||
|
||||
|
|
@ -117,6 +119,9 @@ const createOrShowMainWindow = () => {
|
|||
}
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
if (autoUpdaterService) {
|
||||
autoUpdaterService.setMainWindow(null);
|
||||
}
|
||||
});
|
||||
|
||||
// Update tRPC handler to include the main window
|
||||
|
|
@ -124,6 +129,11 @@ const createOrShowMainWindow = () => {
|
|||
router,
|
||||
windows: [mainWindow, floatingButtonWindow].filter(Boolean) as BrowserWindow[],
|
||||
});
|
||||
|
||||
// Set main window reference for auto-updater
|
||||
if (autoUpdaterService) {
|
||||
autoUpdaterService.setMainWindow(mainWindow);
|
||||
}
|
||||
};
|
||||
|
||||
const createFloatingButtonWindow = () => {
|
||||
|
|
@ -215,6 +225,19 @@ app.on('ready', async () => {
|
|||
// Initialize Contextual Transcription Manager
|
||||
contextualTranscriptionManager = new ContextualTranscriptionManager(modelManagerService);
|
||||
|
||||
// Initialize Auto-Updater Service
|
||||
autoUpdaterService = new AutoUpdaterService();
|
||||
|
||||
// Make auto-updater service available globally for tRPC
|
||||
(globalThis as any).autoUpdaterService = autoUpdaterService;
|
||||
|
||||
// Check for updates on startup (after a brief delay)
|
||||
setTimeout(() => {
|
||||
if (autoUpdaterService) {
|
||||
autoUpdaterService.checkForUpdatesAndNotify();
|
||||
}
|
||||
}, 5000); // Wait 5 seconds after startup
|
||||
|
||||
// Initialize AI service with the appropriate client based on configuration
|
||||
try {
|
||||
const transcriptionClient = createTranscriptionClient();
|
||||
|
|
@ -456,6 +479,7 @@ app.on('ready', async () => {
|
|||
await swiftIOBridgeClientInstance!.call('restoreSystemAudio', {});
|
||||
});
|
||||
|
||||
|
||||
// Initialize the SwiftIOBridgeClient
|
||||
swiftIOBridgeClientInstance = new SwiftIOBridge();
|
||||
|
||||
|
|
@ -512,7 +536,11 @@ app.on('ready', async () => {
|
|||
// Handle unexpected close, maybe attempt restart
|
||||
});
|
||||
|
||||
setupApplicationMenu(createOrShowMainWindow);
|
||||
setupApplicationMenu(createOrShowMainWindow, () => {
|
||||
if (autoUpdaterService) {
|
||||
autoUpdaterService.checkForUpdates(true);
|
||||
}
|
||||
});
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,10 @@ import { app, Menu, MenuItemConstructorOptions, BrowserWindow } from 'electron';
|
|||
// Forward declaration or import of the function type if it's complex
|
||||
// For simplicity, we assume createOrShowSettingsWindow is a () => void function
|
||||
|
||||
export const setupApplicationMenu = (createOrShowSettingsWindow: () => void) => {
|
||||
export const setupApplicationMenu = (
|
||||
createOrShowSettingsWindow: () => void,
|
||||
checkForUpdates?: () => void
|
||||
) => {
|
||||
const menuTemplate: MenuItemConstructorOptions[] = [
|
||||
// { role: 'appMenu' } for macOS
|
||||
...(process.platform === 'darwin'
|
||||
|
|
@ -13,6 +16,10 @@ export const setupApplicationMenu = (createOrShowSettingsWindow: () => void) =>
|
|||
submenu: [
|
||||
{ role: 'about' as const },
|
||||
{ type: 'separator' as const },
|
||||
...(checkForUpdates ? [{
|
||||
label: 'Check for Updates...',
|
||||
click: () => checkForUpdates(),
|
||||
} as MenuItemConstructorOptions, { type: 'separator' as const }] : []),
|
||||
{
|
||||
label: 'Settings',
|
||||
accelerator: 'CmdOrCtrl+,',
|
||||
|
|
@ -109,6 +116,10 @@ export const setupApplicationMenu = (createOrShowSettingsWindow: () => void) =>
|
|||
{
|
||||
role: 'help' as const,
|
||||
submenu: [
|
||||
...(checkForUpdates ? [{
|
||||
label: 'Check for Updates...',
|
||||
click: () => checkForUpdates(),
|
||||
} as MenuItemConstructorOptions, { type: 'separator' as const }] : []),
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
|
|
|
|||
|
|
@ -97,6 +97,7 @@ const api: ElectronAPI = {
|
|||
searchTranscriptions: (searchTerm: string, limit?: number) =>
|
||||
ipcRenderer.invoke('search-transcriptions', searchTerm, limit),
|
||||
|
||||
|
||||
// Vocabulary Database API
|
||||
on: (channel: string, callback: (...args: any[]) => void) => {
|
||||
const handler = (_event: IpcRendererEvent, ...args: any[]) => callback(...args);
|
||||
|
|
|
|||
222
apps/desktop/src/main/services/auto-updater.ts
Normal file
222
apps/desktop/src/main/services/auto-updater.ts
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
import { autoUpdater } from 'electron-updater';
|
||||
import { app, dialog, BrowserWindow } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
import { logger } from '../logger';
|
||||
|
||||
export class AutoUpdaterService extends EventEmitter {
|
||||
private checkingForUpdate = false;
|
||||
private updateAvailable = false;
|
||||
private mainWindow: BrowserWindow | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// Only set up auto-updater in production
|
||||
if (process.env.NODE_ENV !== 'development' && app.isPackaged) {
|
||||
this.setupAutoUpdater();
|
||||
} else {
|
||||
logger.updater.info('Auto-updater disabled in development mode');
|
||||
}
|
||||
}
|
||||
|
||||
setMainWindow(window: BrowserWindow | null) {
|
||||
this.mainWindow = window;
|
||||
}
|
||||
|
||||
private setupAutoUpdater() {
|
||||
// Configure updater
|
||||
autoUpdater.autoDownload = false; // Don't auto-download, ask user first
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
// Development settings
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// In development, you can test with a local update server
|
||||
// autoUpdater.updateConfigPath = path.join(__dirname, 'dev-app-update.yml');
|
||||
autoUpdater.forceDevUpdateConfig = true;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
autoUpdater.on('checking-for-update', () => {
|
||||
logger.updater.info('Checking for update...');
|
||||
this.checkingForUpdate = true;
|
||||
});
|
||||
|
||||
autoUpdater.on('update-available', (info) => {
|
||||
logger.updater.info('Update available', {
|
||||
version: info.version,
|
||||
releaseDate: info.releaseDate,
|
||||
});
|
||||
this.checkingForUpdate = false;
|
||||
this.updateAvailable = true;
|
||||
this.showUpdateDialog(info);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-not-available', (info) => {
|
||||
logger.updater.info('Update not available', { version: info.version });
|
||||
this.checkingForUpdate = false;
|
||||
this.updateAvailable = false;
|
||||
});
|
||||
|
||||
autoUpdater.on('error', (err) => {
|
||||
logger.updater.error('Error in auto-updater', { error: err.message });
|
||||
this.checkingForUpdate = false;
|
||||
|
||||
// Show error dialog only if user manually checked for updates
|
||||
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
dialog.showErrorBox('Update Error', `Error checking for updates: ${err.message}`);
|
||||
}
|
||||
});
|
||||
|
||||
autoUpdater.on('download-progress', (progressObj) => {
|
||||
logger.updater.info('Download progress', {
|
||||
bytesPerSecond: progressObj.bytesPerSecond,
|
||||
percent: progressObj.percent,
|
||||
transferred: progressObj.transferred,
|
||||
total: progressObj.total,
|
||||
});
|
||||
|
||||
// Emit event for tRPC subscription
|
||||
this.emit('download-progress', progressObj);
|
||||
});
|
||||
|
||||
autoUpdater.on('update-downloaded', (info) => {
|
||||
logger.updater.info('Update downloaded', { version: info.version });
|
||||
this.showInstallDialog(info);
|
||||
});
|
||||
}
|
||||
|
||||
private async showUpdateDialog(info: any) {
|
||||
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await dialog.showMessageBox(this.mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Update Available',
|
||||
message: `A new version (${info.version}) is available.`,
|
||||
detail: 'Would you like to download it now? The update will be installed when you restart the app.',
|
||||
buttons: ['Download Now', 'Later'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
});
|
||||
|
||||
if (result.response === 0) {
|
||||
logger.updater.info('User chose to download update');
|
||||
autoUpdater.downloadUpdate();
|
||||
} else {
|
||||
logger.updater.info('User chose to skip update');
|
||||
}
|
||||
}
|
||||
|
||||
private async showInstallDialog(info: any) {
|
||||
if (!this.mainWindow || this.mainWindow.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await dialog.showMessageBox(this.mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Update Ready',
|
||||
message: `Update ${info.version} has been downloaded.`,
|
||||
detail: 'The update will be installed when you restart the app. Would you like to restart now?',
|
||||
buttons: ['Restart Now', 'Later'],
|
||||
defaultId: 0,
|
||||
cancelId: 1,
|
||||
});
|
||||
|
||||
if (result.response === 0) {
|
||||
logger.updater.info('User chose to restart and install update');
|
||||
autoUpdater.quitAndInstall();
|
||||
} else {
|
||||
logger.updater.info('User chose to install update later');
|
||||
}
|
||||
}
|
||||
|
||||
async checkForUpdates(userInitiated = false): Promise<void> {
|
||||
// Skip in development
|
||||
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
|
||||
logger.updater.info('Skipping update check in development mode');
|
||||
if (userInitiated && this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
dialog.showMessageBox(this.mainWindow, {
|
||||
type: 'info',
|
||||
title: 'Development Mode',
|
||||
message: 'Update checking is disabled in development mode.',
|
||||
buttons: ['OK']
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.checkingForUpdate) {
|
||||
logger.updater.info('Already checking for updates');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.updater.info('Starting update check', { userInitiated });
|
||||
await autoUpdater.checkForUpdates();
|
||||
} catch (error) {
|
||||
logger.updater.error('Failed to check for updates', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
|
||||
if (userInitiated && this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||
dialog.showErrorBox('Update Check Failed', 'Failed to check for updates. Please try again later.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async checkForUpdatesAndNotify(): Promise<void> {
|
||||
// Skip in development
|
||||
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
|
||||
logger.updater.info('Skipping background update check in development mode');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await autoUpdater.checkForUpdatesAndNotify();
|
||||
} catch (error) {
|
||||
logger.updater.error('Failed to check for updates and notify', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isCheckingForUpdate(): boolean {
|
||||
return this.checkingForUpdate;
|
||||
}
|
||||
|
||||
isUpdateAvailable(): boolean {
|
||||
return this.updateAvailable;
|
||||
}
|
||||
|
||||
async downloadUpdate(): Promise<void> {
|
||||
// Skip in development
|
||||
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
|
||||
logger.updater.info('Skipping update download in development mode');
|
||||
throw new Error('Update downloads are disabled in development mode');
|
||||
}
|
||||
|
||||
if (!this.updateAvailable) {
|
||||
throw new Error('No update available to download');
|
||||
}
|
||||
|
||||
try {
|
||||
await autoUpdater.downloadUpdate();
|
||||
} catch (error) {
|
||||
logger.updater.error('Failed to download update', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
quitAndInstall(): void {
|
||||
// Skip in development
|
||||
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
|
||||
logger.updater.info('Skipping quit and install in development mode');
|
||||
return;
|
||||
}
|
||||
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import { vocabularyRouter } from './routers/vocabulary';
|
|||
import { transcriptionsRouter } from './routers/transcriptions';
|
||||
import { modelsRouter } from './routers/models';
|
||||
import { settingsRouter } from './routers/settings';
|
||||
import { updaterRouter } from './routers/updater';
|
||||
|
||||
const t = initTRPC.create({
|
||||
isServer: true,
|
||||
|
|
@ -47,6 +48,9 @@ export const router = t.router({
|
|||
|
||||
// Settings router
|
||||
settings: settingsRouter,
|
||||
|
||||
// Auto-updater router
|
||||
updater: updaterRouter,
|
||||
});
|
||||
|
||||
export type AppRouter = typeof router;
|
||||
|
|
|
|||
165
apps/desktop/src/trpc/routers/updater.ts
Normal file
165
apps/desktop/src/trpc/routers/updater.ts
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import { initTRPC } from '@trpc/server';
|
||||
import superjson from 'superjson';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Download progress type from electron-updater
|
||||
interface DownloadProgress {
|
||||
bytesPerSecond: number;
|
||||
percent: number;
|
||||
transferred: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
const t = initTRPC.create({
|
||||
isServer: true,
|
||||
transformer: superjson,
|
||||
});
|
||||
|
||||
// We'll need to access the auto-updater service from the main process
|
||||
declare global {
|
||||
var autoUpdaterService: any;
|
||||
var logger: any;
|
||||
}
|
||||
|
||||
export const updaterRouter = t.router({
|
||||
// Check for updates (manual trigger)
|
||||
checkForUpdates: t.procedure
|
||||
.input(z.object({ userInitiated: z.boolean().optional().default(false) }).optional())
|
||||
.mutation(async ({ input }) => {
|
||||
try {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
throw new Error('Auto-updater service not available');
|
||||
}
|
||||
|
||||
const userInitiated = input?.userInitiated ?? false;
|
||||
await globalThis.autoUpdaterService.checkForUpdates(userInitiated);
|
||||
globalThis.logger?.updater.info('Update check initiated via tRPC', { userInitiated });
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
globalThis.logger?.updater.error('Error checking for updates via tRPC', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
// Check for updates and notify (background check)
|
||||
checkForUpdatesAndNotify: t.procedure.mutation(async () => {
|
||||
try {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
throw new Error('Auto-updater service not available');
|
||||
}
|
||||
|
||||
await globalThis.autoUpdaterService.checkForUpdatesAndNotify();
|
||||
globalThis.logger?.updater.info('Background update check initiated via tRPC');
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
globalThis.logger?.updater.error('Error in background update check via tRPC', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
// Download available update
|
||||
downloadUpdate: t.procedure.mutation(async () => {
|
||||
try {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
throw new Error('Auto-updater service not available');
|
||||
}
|
||||
|
||||
await globalThis.autoUpdaterService.downloadUpdate();
|
||||
globalThis.logger?.updater.info('Update download initiated via tRPC');
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
globalThis.logger?.updater.error('Error downloading update via tRPC', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
// Quit and install update
|
||||
quitAndInstall: t.procedure.mutation(async () => {
|
||||
try {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
throw new Error('Auto-updater service not available');
|
||||
}
|
||||
|
||||
globalThis.logger?.updater.info('Quit and install initiated via tRPC');
|
||||
globalThis.autoUpdaterService.quitAndInstall();
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
globalThis.logger?.updater.error('Error quitting and installing via tRPC', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
// Get current update checking status
|
||||
isCheckingForUpdate: t.procedure.query(async () => {
|
||||
try {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return globalThis.autoUpdaterService.isCheckingForUpdate();
|
||||
} catch (error) {
|
||||
globalThis.logger?.updater.error('Error getting update checking status via tRPC', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
// Get current update available status
|
||||
isUpdateAvailable: t.procedure.query(async () => {
|
||||
try {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return globalThis.autoUpdaterService.isUpdateAvailable();
|
||||
} catch (error) {
|
||||
globalThis.logger?.updater.error('Error getting update available status via tRPC', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
// Subscribe to download progress updates
|
||||
onDownloadProgress: t.procedure.subscription(async function* () {
|
||||
if (!globalThis.autoUpdaterService) {
|
||||
throw new Error('Auto-updater service not initialized');
|
||||
}
|
||||
|
||||
const eventQueue: Array<DownloadProgress> = [];
|
||||
|
||||
const handleDownloadProgress = (progressObj: DownloadProgress) => {
|
||||
eventQueue.push(progressObj);
|
||||
};
|
||||
|
||||
globalThis.autoUpdaterService.on('download-progress', handleDownloadProgress);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
while (eventQueue.length > 0) {
|
||||
const progress = eventQueue.shift();
|
||||
if (progress) {
|
||||
yield progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
globalThis.autoUpdaterService?.off('download-progress', handleDownloadProgress);
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
|
@ -63,6 +63,7 @@ export interface ElectronAPI {
|
|||
limit?: number
|
||||
) => Promise<import('../db/schema').Transcription[]>;
|
||||
|
||||
|
||||
on: (channel: string, callback: (...args: any[]) => void) => void;
|
||||
off: (channel: string, callback: (...args: any[]) => void) => void;
|
||||
|
||||
|
|
|
|||
257
pnpm-lock.yaml
generated
257
pnpm-lock.yaml
generated
|
|
@ -188,6 +188,9 @@ importers:
|
|||
electron-trpc-experimental:
|
||||
specifier: 1.0.0-alpha.1
|
||||
version: 1.0.0-alpha.1(@trpc/client@11.4.2(@trpc/server@11.4.2(typescript@5.8.3))(typescript@5.8.3))(@trpc/server@11.4.2(typescript@5.8.3))(electron@36.2.0)
|
||||
electron-updater:
|
||||
specifier: ^6.6.2
|
||||
version: 6.6.2
|
||||
embla-carousel-react:
|
||||
specifier: ^8.6.0
|
||||
version: 8.6.0(react@19.1.0)
|
||||
|
|
@ -285,6 +288,9 @@ importers:
|
|||
'@electron-forge/plugin-vite':
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1
|
||||
'@electron-forge/publisher-github':
|
||||
specifier: ^7.8.1
|
||||
version: 7.8.1(encoding@0.1.13)
|
||||
'@electron/fuses':
|
||||
specifier: ^1.8.0
|
||||
version: 1.8.0
|
||||
|
|
@ -903,6 +909,10 @@ packages:
|
|||
resolution: {integrity: sha512-z2C+C4pcFxyCXIFwXGDcxhU8qtVUPZa3sPL6tH5RuMxJi77768chLw2quDWk2/dfupcSELXcOMYCs7aLysCzeQ==}
|
||||
engines: {node: '>= 16.4.0'}
|
||||
|
||||
'@electron-forge/publisher-github@7.8.1':
|
||||
resolution: {integrity: sha512-hjRSJ3/JwKHgUNuvJo4vTPBJQQyvF72QOudHr+WSXMSAPasTwfDhvGaTS54mQqcKlOQ53cwHQjWO0xVEwQYQ0g==}
|
||||
engines: {node: '>= 16.4.0'}
|
||||
|
||||
'@electron-forge/shared-types@7.8.1':
|
||||
resolution: {integrity: sha512-guLyGjIISKQQRWHX+ugmcjIOjn2q/BEzCo3ioJXFowxiFwmZw/oCZ2KlPig/t6dMqgUrHTH5W/F0WKu0EY4M+Q==}
|
||||
engines: {node: '>= 16.4.0'}
|
||||
|
|
@ -1946,6 +1956,61 @@ packages:
|
|||
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
|
||||
deprecated: This functionality has been moved to @npmcli/fs
|
||||
|
||||
'@octokit/auth-token@2.5.0':
|
||||
resolution: {integrity: sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==}
|
||||
|
||||
'@octokit/core@3.6.0':
|
||||
resolution: {integrity: sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==}
|
||||
|
||||
'@octokit/endpoint@6.0.12':
|
||||
resolution: {integrity: sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==}
|
||||
|
||||
'@octokit/graphql@4.8.0':
|
||||
resolution: {integrity: sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==}
|
||||
|
||||
'@octokit/openapi-types@12.11.0':
|
||||
resolution: {integrity: sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==}
|
||||
|
||||
'@octokit/openapi-types@24.2.0':
|
||||
resolution: {integrity: sha512-9sIH3nSUttelJSXUrmGzl7QUBFul0/mB8HRYl3fOlgHbIWG+WnYDXU3v/2zMtAvuzZ/ed00Ei6on975FhBfzrg==}
|
||||
|
||||
'@octokit/plugin-paginate-rest@2.21.3':
|
||||
resolution: {integrity: sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=2'
|
||||
|
||||
'@octokit/plugin-request-log@1.0.4':
|
||||
resolution: {integrity: sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=3'
|
||||
|
||||
'@octokit/plugin-rest-endpoint-methods@5.16.2':
|
||||
resolution: {integrity: sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==}
|
||||
peerDependencies:
|
||||
'@octokit/core': '>=3'
|
||||
|
||||
'@octokit/plugin-retry@3.0.9':
|
||||
resolution: {integrity: sha512-r+fArdP5+TG6l1Rv/C9hVoty6tldw6cE2pRHNGmFPdyfrc696R6JjrQ3d7HdVqGwuzfyrcaLAKD7K8TX8aehUQ==}
|
||||
|
||||
'@octokit/request-error@2.1.0':
|
||||
resolution: {integrity: sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==}
|
||||
|
||||
'@octokit/request-error@5.1.1':
|
||||
resolution: {integrity: sha512-v9iyEQJH6ZntoENr9/yXxjuezh4My67CBSu9r6Ve/05Iu5gNgnisNWOsoJHTP6k0Rr0+HQIpnH+kyammu90q/g==}
|
||||
engines: {node: '>= 18'}
|
||||
|
||||
'@octokit/request@5.6.3':
|
||||
resolution: {integrity: sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==}
|
||||
|
||||
'@octokit/rest@18.12.0':
|
||||
resolution: {integrity: sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==}
|
||||
|
||||
'@octokit/types@13.10.0':
|
||||
resolution: {integrity: sha512-ifLaO34EbbPj0Xgro4G5lP5asESjwHracYJvVaPIyXMuiuXLlhic3S47cBdTb+jfODkTE5YtGCLt3Ay3+J97sA==}
|
||||
|
||||
'@octokit/types@6.41.0':
|
||||
resolution: {integrity: sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==}
|
||||
|
||||
'@opentelemetry/api@1.9.0':
|
||||
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
|
@ -3611,6 +3676,9 @@ packages:
|
|||
batch@0.6.1:
|
||||
resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==}
|
||||
|
||||
before-after-hook@2.2.3:
|
||||
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
|
||||
|
||||
better-sqlite3@11.10.0:
|
||||
resolution: {integrity: sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==}
|
||||
|
||||
|
|
@ -3634,6 +3702,9 @@ packages:
|
|||
resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==}
|
||||
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
|
||||
|
||||
bottleneck@2.19.5:
|
||||
resolution: {integrity: sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==}
|
||||
|
||||
bowser@2.11.0:
|
||||
resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==}
|
||||
|
||||
|
|
@ -3665,6 +3736,10 @@ packages:
|
|||
buffer@6.0.3:
|
||||
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
|
||||
|
||||
builder-util-runtime@9.3.1:
|
||||
resolution: {integrity: sha512-2/egrNDDnRaxVwK3A+cJq6UOlqOdedGA7JPqCeJjN2Zjk1/QB/6QUi3b714ScIGS7HafFXTyzJEOr5b44I3kvQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
|
@ -4181,6 +4256,9 @@ packages:
|
|||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
||||
deprecation@2.3.1:
|
||||
resolution: {integrity: sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==}
|
||||
|
||||
dequal@2.0.3:
|
||||
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
|
||||
engines: {node: '>=6'}
|
||||
|
|
@ -4385,6 +4463,9 @@ packages:
|
|||
'@trpc/server': '>11.0.0'
|
||||
electron: '>19.0.0'
|
||||
|
||||
electron-updater@6.6.2:
|
||||
resolution: {integrity: sha512-Cr4GDOkbAUqRHP5/oeOmH/L2Bn6+FQPxVLZtPbcmKZC63a1F3uu5EefYOssgZXG3u/zBlubbJ5PJdITdMVggbw==}
|
||||
|
||||
electron-winstaller@5.4.0:
|
||||
resolution: {integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==}
|
||||
engines: {node: '>=8.0.0'}
|
||||
|
|
@ -5513,6 +5594,10 @@ packages:
|
|||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
is-plain-object@5.0.0:
|
||||
resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
is-promise@2.2.2:
|
||||
resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
|
||||
|
||||
|
|
@ -5680,6 +5765,9 @@ packages:
|
|||
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
lazy-val@1.0.5:
|
||||
resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -5778,6 +5866,9 @@ packages:
|
|||
lodash.defaults@4.2.0:
|
||||
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||
|
||||
lodash.escaperegexp@4.1.2:
|
||||
resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==}
|
||||
|
||||
lodash.get@4.4.2:
|
||||
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
|
||||
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
|
||||
|
|
@ -5785,6 +5876,10 @@ packages:
|
|||
lodash.isarguments@3.1.0:
|
||||
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||
|
||||
lodash.isequal@4.5.0:
|
||||
resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==}
|
||||
deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead.
|
||||
|
||||
lodash.merge@4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
|
||||
|
|
@ -7106,6 +7201,9 @@ packages:
|
|||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
sax@1.4.1:
|
||||
resolution: {integrity: sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==}
|
||||
|
||||
scheduler@0.26.0:
|
||||
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
||||
|
||||
|
|
@ -7569,6 +7667,9 @@ packages:
|
|||
tiny-invariant@1.3.3:
|
||||
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
|
||||
|
||||
tiny-typed-emitter@2.1.0:
|
||||
resolution: {integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==}
|
||||
|
||||
tinycolor2@1.6.0:
|
||||
resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
|
||||
|
||||
|
|
@ -7841,6 +7942,9 @@ packages:
|
|||
unist-util-visit@5.0.0:
|
||||
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
|
||||
|
||||
universal-user-agent@6.0.1:
|
||||
resolution: {integrity: sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==}
|
||||
|
||||
universalify@0.1.2:
|
||||
resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
|
||||
engines: {node: '>= 4.0.0'}
|
||||
|
|
@ -8972,6 +9076,25 @@ snapshots:
|
|||
- bluebird
|
||||
- supports-color
|
||||
|
||||
'@electron-forge/publisher-github@7.8.1(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@electron-forge/publisher-base': 7.8.1
|
||||
'@electron-forge/shared-types': 7.8.1
|
||||
'@octokit/core': 3.6.0(encoding@0.1.13)
|
||||
'@octokit/plugin-retry': 3.0.9
|
||||
'@octokit/request-error': 5.1.1
|
||||
'@octokit/rest': 18.12.0(encoding@0.1.13)
|
||||
'@octokit/types': 6.41.0
|
||||
chalk: 4.1.2
|
||||
debug: 4.4.1
|
||||
fs-extra: 10.1.0
|
||||
log-symbols: 4.1.0
|
||||
mime-types: 2.1.35
|
||||
transitivePeerDependencies:
|
||||
- bluebird
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@electron-forge/shared-types@7.8.1':
|
||||
dependencies:
|
||||
'@electron-forge/tracer': 7.8.1
|
||||
|
|
@ -9893,6 +10016,100 @@ snapshots:
|
|||
mkdirp: 1.0.4
|
||||
rimraf: 3.0.2
|
||||
|
||||
'@octokit/auth-token@2.5.0':
|
||||
dependencies:
|
||||
'@octokit/types': 6.41.0
|
||||
|
||||
'@octokit/core@3.6.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@octokit/auth-token': 2.5.0
|
||||
'@octokit/graphql': 4.8.0(encoding@0.1.13)
|
||||
'@octokit/request': 5.6.3(encoding@0.1.13)
|
||||
'@octokit/request-error': 2.1.0
|
||||
'@octokit/types': 6.41.0
|
||||
before-after-hook: 2.2.3
|
||||
universal-user-agent: 6.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@octokit/endpoint@6.0.12':
|
||||
dependencies:
|
||||
'@octokit/types': 6.41.0
|
||||
is-plain-object: 5.0.0
|
||||
universal-user-agent: 6.0.1
|
||||
|
||||
'@octokit/graphql@4.8.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@octokit/request': 5.6.3(encoding@0.1.13)
|
||||
'@octokit/types': 6.41.0
|
||||
universal-user-agent: 6.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@octokit/openapi-types@12.11.0': {}
|
||||
|
||||
'@octokit/openapi-types@24.2.0': {}
|
||||
|
||||
'@octokit/plugin-paginate-rest@2.21.3(@octokit/core@3.6.0(encoding@0.1.13))':
|
||||
dependencies:
|
||||
'@octokit/core': 3.6.0(encoding@0.1.13)
|
||||
'@octokit/types': 6.41.0
|
||||
|
||||
'@octokit/plugin-request-log@1.0.4(@octokit/core@3.6.0(encoding@0.1.13))':
|
||||
dependencies:
|
||||
'@octokit/core': 3.6.0(encoding@0.1.13)
|
||||
|
||||
'@octokit/plugin-rest-endpoint-methods@5.16.2(@octokit/core@3.6.0(encoding@0.1.13))':
|
||||
dependencies:
|
||||
'@octokit/core': 3.6.0(encoding@0.1.13)
|
||||
'@octokit/types': 6.41.0
|
||||
deprecation: 2.3.1
|
||||
|
||||
'@octokit/plugin-retry@3.0.9':
|
||||
dependencies:
|
||||
'@octokit/types': 6.41.0
|
||||
bottleneck: 2.19.5
|
||||
|
||||
'@octokit/request-error@2.1.0':
|
||||
dependencies:
|
||||
'@octokit/types': 6.41.0
|
||||
deprecation: 2.3.1
|
||||
once: 1.4.0
|
||||
|
||||
'@octokit/request-error@5.1.1':
|
||||
dependencies:
|
||||
'@octokit/types': 13.10.0
|
||||
deprecation: 2.3.1
|
||||
once: 1.4.0
|
||||
|
||||
'@octokit/request@5.6.3(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@octokit/endpoint': 6.0.12
|
||||
'@octokit/request-error': 2.1.0
|
||||
'@octokit/types': 6.41.0
|
||||
is-plain-object: 5.0.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
universal-user-agent: 6.0.1
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@octokit/rest@18.12.0(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@octokit/core': 3.6.0(encoding@0.1.13)
|
||||
'@octokit/plugin-paginate-rest': 2.21.3(@octokit/core@3.6.0(encoding@0.1.13))
|
||||
'@octokit/plugin-request-log': 1.0.4(@octokit/core@3.6.0(encoding@0.1.13))
|
||||
'@octokit/plugin-rest-endpoint-methods': 5.16.2(@octokit/core@3.6.0(encoding@0.1.13))
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@octokit/types@13.10.0':
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 24.2.0
|
||||
|
||||
'@octokit/types@6.41.0':
|
||||
dependencies:
|
||||
'@octokit/openapi-types': 12.11.0
|
||||
|
||||
'@opentelemetry/api@1.9.0': {}
|
||||
|
||||
'@orama/orama@3.1.7': {}
|
||||
|
|
@ -11720,6 +11937,8 @@ snapshots:
|
|||
|
||||
batch@0.6.1: {}
|
||||
|
||||
before-after-hook@2.2.3: {}
|
||||
|
||||
better-sqlite3@11.10.0:
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
|
|
@ -11761,6 +11980,8 @@ snapshots:
|
|||
boolean@3.2.0:
|
||||
optional: true
|
||||
|
||||
bottleneck@2.19.5: {}
|
||||
|
||||
bowser@2.11.0: {}
|
||||
|
||||
bplist-creator@0.0.8:
|
||||
|
|
@ -11797,6 +12018,13 @@ snapshots:
|
|||
base64-js: 1.5.1
|
||||
ieee754: 1.2.1
|
||||
|
||||
builder-util-runtime@9.3.1:
|
||||
dependencies:
|
||||
debug: 4.4.1
|
||||
sax: 1.4.1
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
busboy@1.6.0:
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
|
|
@ -12314,6 +12542,8 @@ snapshots:
|
|||
|
||||
depd@2.0.0: {}
|
||||
|
||||
deprecation@2.3.1: {}
|
||||
|
||||
dequal@2.0.3: {}
|
||||
|
||||
destroy@1.2.0: {}
|
||||
|
|
@ -12475,6 +12705,19 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron-updater@6.6.2:
|
||||
dependencies:
|
||||
builder-util-runtime: 9.3.1
|
||||
fs-extra: 10.1.0
|
||||
js-yaml: 4.1.0
|
||||
lazy-val: 1.0.5
|
||||
lodash.escaperegexp: 4.1.2
|
||||
lodash.isequal: 4.5.0
|
||||
semver: 7.7.2
|
||||
tiny-typed-emitter: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
electron-winstaller@5.4.0:
|
||||
dependencies:
|
||||
'@electron/asar': 3.4.1
|
||||
|
|
@ -14059,6 +14302,8 @@ snapshots:
|
|||
|
||||
is-plain-obj@4.1.0: {}
|
||||
|
||||
is-plain-object@5.0.0: {}
|
||||
|
||||
is-promise@2.2.2: {}
|
||||
|
||||
is-property@1.0.2:
|
||||
|
|
@ -14220,6 +14465,8 @@ snapshots:
|
|||
|
||||
kind-of@6.0.3: {}
|
||||
|
||||
lazy-val@1.0.5: {}
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
|
|
@ -14318,10 +14565,14 @@ snapshots:
|
|||
|
||||
lodash.defaults@4.2.0: {}
|
||||
|
||||
lodash.escaperegexp@4.1.2: {}
|
||||
|
||||
lodash.get@4.4.2: {}
|
||||
|
||||
lodash.isarguments@3.1.0: {}
|
||||
|
||||
lodash.isequal@4.5.0: {}
|
||||
|
||||
lodash.merge@4.6.2: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
|
|
@ -16130,6 +16381,8 @@ snapshots:
|
|||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
sax@1.4.1: {}
|
||||
|
||||
scheduler@0.26.0: {}
|
||||
|
||||
scroll-into-view-if-needed@3.1.0:
|
||||
|
|
@ -16747,6 +17000,8 @@ snapshots:
|
|||
|
||||
tiny-invariant@1.3.3: {}
|
||||
|
||||
tiny-typed-emitter@2.1.0: {}
|
||||
|
||||
tinycolor2@1.6.0: {}
|
||||
|
||||
tinygradient@1.1.5:
|
||||
|
|
@ -17052,6 +17307,8 @@ snapshots:
|
|||
unist-util-is: 6.0.0
|
||||
unist-util-visit-parents: 6.0.1
|
||||
|
||||
universal-user-agent@6.0.1: {}
|
||||
|
||||
universalify@0.1.2: {}
|
||||
|
||||
universalify@2.0.1: {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue