chore: git publish + auto update handling (#31)

* chore: git publish + auto update handling
This commit is contained in:
Haritabh 2025-06-28 09:43:46 +05:30 committed by GitHub
parent d05bf80f10
commit aecabbeb4a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 957 additions and 3 deletions

View file

@ -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;

View file

@ -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",

View 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>
);
}

View file

@ -111,6 +111,7 @@ export const logger = {
swift: createLoggerForScope('swift'),
ui: createLoggerForScope('ui'),
db: createLoggerForScope('db'),
updater: createLoggerForScope('updater'),
};
// Log startup information

View file

@ -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 {

View file

@ -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 () => {

View file

@ -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);

View 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();
}
}

View file

@ -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;

View 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);
}
}),
});

View file

@ -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
View file

@ -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: {}