From bb1fb1ed6d188ee636c4593bf23e789d112de4ee Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Thu, 19 Mar 2026 23:27:49 +0100 Subject: [PATCH 01/20] feat (setup): add data folder picker to first-run setup --- electron/main/ipc-handlers.ts | 49 ++++++++++++++--------- electron/main/python-bridge.ts | 6 +-- electron/main/settings-store.ts | 10 +++-- electron/preload/index.ts | 12 +++--- src/areas/setup/FirstRunSetup.tsx | 64 +++++++++++++++++++++++++++---- src/shared/stores/appStore.ts | 22 +++++++---- src/shared/types/electron.d.ts | 23 +++++------ 7 files changed, 131 insertions(+), 55 deletions(-) diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index b2bf758..6358399 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -32,9 +32,19 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe // Setup handlers — skipped in dev (uses .venv instead of python-embed) ipcMain.handle('setup:check', async () => { - if (!app.isPackaged) return { needed: false } + const defaultDataDir = join(app.getPath('documents'), 'LocalMeshy') + if (!app.isPackaged) return { needed: false, defaultDataDir } const userData = app.getPath('userData') - return { needed: checkSetupNeeded(userData) } + return { needed: checkSetupNeeded(userData), defaultDataDir } + }) + + ipcMain.handle('setup:saveDataDir', async (_event, { baseDir }: { baseDir: string }) => { + const userData = app.getPath('userData') + setSettings(userData, { + modelsDir: join(baseDir, 'models'), + workspaceDir: join(baseDir, 'workspace'), + extensionsDir: join(baseDir, 'extensions'), + }) }) ipcMain.handle('setup:run', async () => { @@ -97,7 +107,7 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe }) ipcMain.handle('model:delete', async (_, modelId: string): Promise<{ success: boolean; error?: string }> => { - const modelDir = join(app.getPath('userData'), 'models', modelId) + const modelDir = join(getSettings(app.getPath('userData')).modelsDir, modelId) try { await axios.post(`${API_BASE_URL}/model/unload/${encodeURIComponent(modelId)}`, {}, { timeout: 5000 }) } catch { @@ -119,12 +129,12 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe // Model management ipcMain.handle('model:listDownloaded', () => { - const modelsDir = join(app.getPath('userData'), 'models') + const modelsDir = getSettings(app.getPath('userData')).modelsDir return listDownloadedModels(modelsDir) }) ipcMain.handle('model:isDownloaded', (_, modelId: string): boolean => { - const modelsDir = join(app.getPath('userData'), 'models') + const modelsDir = getSettings(app.getPath('userData')).modelsDir return isModelDownloaded(modelsDir, modelId) }) @@ -170,7 +180,7 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe ipcMain.handle('app:info', () => ({ version: app.getVersion(), userData: app.getPath('userData'), - modelsDir: join(app.getPath('userData'), 'models'), + modelsDir: getSettings(app.getPath('userData')).modelsDir, apiUrl: API_BASE_URL })) @@ -179,7 +189,7 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe return getSettings(app.getPath('userData')) }) - ipcMain.handle('settings:set', (_event, patch: { modelsDir?: string; workspaceDir?: string }) => { + ipcMain.handle('settings:set', (_event, patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }) => { return setSettings(app.getPath('userData'), patch) }) @@ -211,7 +221,8 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe }) // Workspace filesystem-based persistence - const workspacePath = (...parts: string[]) => join(app.getPath('userData'), 'workspace', ...parts) + const workspacePath = (...parts: string[]) => + join(getSettings(app.getPath('userData')).workspaceDir, ...parts) ipcMain.handle('workspace:listCollections', async () => { const base = workspacePath() @@ -276,10 +287,11 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe ipcMain.handle('fs:deleteDirectory', async (_, dirPath: string) => { const userData = app.getPath('userData') + const settings = getSettings(userData) const allowedRoots = [ - join(userData, 'models'), - join(userData, 'workspace'), - join(userData, 'extensions'), + settings.modelsDir, + settings.workspaceDir, + settings.extensionsDir, join(userData, 'gen-cache'), ] const resolved = join(dirPath) @@ -358,9 +370,9 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe } } - // Extensions — reads %appdata%/Modly/extensions + // Extensions — reads configured extensions directory ipcMain.handle('extensions:list', async () => { - const extensionsDir = join(app.getPath('userData'), 'extensions') + const extensionsDir = getSettings(app.getPath('userData')).extensionsDir try { if (!existsSync(extensionsDir)) return [] const [entries, trustedRepos] = await Promise.all([ @@ -449,7 +461,7 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe await writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8') // 5. Copy to extensions directory (overwrite if already present) - const extensionsDir = join(app.getPath('userData'), 'extensions') + const extensionsDir = getSettings(app.getPath('userData')).extensionsDir await mkdir(extensionsDir, { recursive: true }) const destDir = join(extensionsDir, manifest.id) @@ -481,7 +493,7 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe // Uninstall an extension — deletes its directory and reloads Python ipcMain.handle('extensions:uninstall', async (_, extensionId: string) => { - const extensionsDir = join(app.getPath('userData'), 'extensions') + const extensionsDir = getSettings(app.getPath('userData')).extensionsDir const extPath = join(extensionsDir, extensionId) try { await rmAsync(extPath, { recursive: true, force: true }) @@ -506,11 +518,12 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe }) // Update FastAPI paths at runtime (without restarting) - ipcMain.handle('api:updatePaths', async (_event, patch: { modelsDir?: string; workspaceDir?: string }) => { + ipcMain.handle('api:updatePaths', async (_event, patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }) => { try { await axios.post(`${API_BASE_URL}/settings/paths`, { - models_dir: patch.modelsDir, - workspace_dir: patch.workspaceDir, + models_dir: patch.modelsDir, + workspace_dir: patch.workspaceDir, + extensions_dir: patch.extensionsDir, }) return { success: true } } catch (err) { diff --git a/electron/main/python-bridge.ts b/electron/main/python-bridge.ts index 2664f29..e6d0801 100644 --- a/electron/main/python-bridge.ts +++ b/electron/main/python-bridge.ts @@ -229,9 +229,9 @@ export class PythonBridge { } private resolveExtensionsDir(): string { - const dir = join(app.getPath('userData'), 'extensions') - mkdirSync(dir, { recursive: true }) - return dir + const s = getSettings(app.getPath('userData')) + mkdirSync(s.extensionsDir, { recursive: true }) + return s.extensionsDir } } diff --git a/electron/main/settings-store.ts b/electron/main/settings-store.ts index 3186b16..c12fb7e 100644 --- a/electron/main/settings-store.ts +++ b/electron/main/settings-store.ts @@ -2,8 +2,9 @@ import { join } from 'path' import { readFileSync, writeFileSync, existsSync } from 'fs' export interface AppSettings { - modelsDir: string - workspaceDir: string + modelsDir: string + workspaceDir: string + extensionsDir: string } function settingsPath(userData: string): string { @@ -12,8 +13,9 @@ function settingsPath(userData: string): string { export function getSettings(userData: string): AppSettings { const defaults: AppSettings = { - modelsDir: join(userData, 'models'), - workspaceDir: join(userData, 'workspace'), + modelsDir: join(userData, 'models'), + workspaceDir: join(userData, 'workspace'), + extensionsDir: join(userData, 'extensions'), } const file = settingsPath(userData) diff --git a/electron/preload/index.ts b/electron/preload/index.ts index a78bab1..b15dc09 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -45,9 +45,9 @@ contextBridge.exposeInMainWorld('electron', { // Settings settings: { - get: (): Promise<{ modelsDir: string; workspaceDir: string }> => + get: (): Promise<{ modelsDir: string; workspaceDir: string; extensionsDir: string }> => ipcRenderer.invoke('settings:get'), - set: (patch: { modelsDir?: string; workspaceDir?: string }): Promise<{ modelsDir: string; workspaceDir: string }> => + set: (patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }): Promise<{ modelsDir: string; workspaceDir: string; extensionsDir: string }> => ipcRenderer.invoke('settings:set', patch), }, @@ -59,7 +59,7 @@ contextBridge.exposeInMainWorld('electron', { // API helpers (calls FastAPI from the main process) api: { - updatePaths: (patch: { modelsDir?: string; workspaceDir?: string }): Promise<{ success: boolean; error?: string }> => + updatePaths: (patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('api:updatePaths', patch), }, @@ -144,10 +144,12 @@ contextBridge.exposeInMainWorld('electron', { // First-run setup setup: { - check: (): Promise<{ needed: boolean }> => + check: (): Promise<{ needed: boolean; defaultDataDir: string }> => ipcRenderer.invoke('setup:check'), - run: (): Promise<{ success: boolean; error?: string }> => + run: (): Promise<{ success: boolean; error?: string }> => ipcRenderer.invoke('setup:run'), + saveDataDir: (baseDir: string): Promise => + ipcRenderer.invoke('setup:saveDataDir', { baseDir }), onProgress: (cb: (data: { step: string; percent: number; currentPackage?: string }) => void) => { ipcRenderer.on('setup:progress', (_e, data) => cb(data)) }, diff --git a/src/areas/setup/FirstRunSetup.tsx b/src/areas/setup/FirstRunSetup.tsx index 518e36f..720067e 100644 --- a/src/areas/setup/FirstRunSetup.tsx +++ b/src/areas/setup/FirstRunSetup.tsx @@ -1,4 +1,4 @@ -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { useAppStore, SetupProgress } from '@shared/stores/appStore' // ─── Logo (shared) ────────────────────────────────────────────────────────── @@ -47,6 +47,59 @@ function CheckingPanel(): JSX.Element { ) } +function ChoosePathPanel({ + defaultPath, + onConfirm, +}: { + defaultPath: string + onConfirm: (path: string) => void +}): JSX.Element { + const [selectedPath, setSelectedPath] = useState(defaultPath || '') + + // Sync if defaultPath arrives after mount (async IPC) + useEffect(() => { + if (defaultPath && !selectedPath) setSelectedPath(defaultPath) + }, [defaultPath]) + + async function handleBrowse() { + const picked = await window.electron.fs.selectDirectory() + if (picked) setSelectedPath(picked) + } + + return ( +
+

Choose a data folder

+

+ Models can be several GB each. Choose a folder on a drive with plenty of free space — + preferably not your system drive (C:). +

+ + {/* Path display */} +
+
+

+ {selectedPath || 'No folder selected'} +

+
+ +
+ + +
+ ) +} + const STEPS = [ { key: 'enabling-site', label: 'Preparing Python' }, { key: 'pip', label: 'Installing pip' }, @@ -135,14 +188,9 @@ function ErrorPanel({ message }: { message: string | null }): JSX.Element { // ─── Main component ───────────────────────────────────────────────────────── export default function FirstRunSetup(): JSX.Element { - const { setupStatus, setupProgress, setupError, runSetup, backendStatus, backendError } = + const { setupStatus, setupProgress, setupError, saveDataDir, defaultDataDir, backendStatus, backendError } = useAppStore() - // Auto-trigger installation when setup is needed - useEffect(() => { - if (setupStatus === 'needed') runSetup() - }, [setupStatus]) - const renderPanel = () => { switch (setupStatus) { case 'idle': @@ -150,6 +198,8 @@ export default function FirstRunSetup(): JSX.Element { return case 'needed': + return + case 'installing': return diff --git a/src/shared/stores/appStore.ts b/src/shared/stores/appStore.ts index a8afa94..8d35d42 100644 --- a/src/shared/stores/appStore.ts +++ b/src/shared/stores/appStore.ts @@ -81,11 +81,13 @@ interface AppState { toggleWorkspacePanel: () => void // Setup - setupStatus: SetupStatus - setupProgress: SetupProgress | null - setupError: string | null - checkSetup: () => Promise - runSetup: () => Promise + setupStatus: SetupStatus + setupProgress: SetupProgress | null + setupError: string | null + defaultDataDir: string + checkSetup: () => Promise + runSetup: () => Promise + saveDataDir: (baseDir: string) => Promise // Actions initApp: () => Promise @@ -104,11 +106,17 @@ export const useAppStore = create()( setupStatus: 'idle', setupProgress: null, setupError: null, + defaultDataDir: '', checkSetup: async () => { set({ setupStatus: 'checking' }) - const { needed } = await window.electron.setup.check() - set({ setupStatus: needed ? 'needed' : 'done' }) + const { needed, defaultDataDir } = await window.electron.setup.check() + set({ setupStatus: needed ? 'needed' : 'done', defaultDataDir }) + }, + + saveDataDir: async (baseDir: string) => { + await window.electron.setup.saveDataDir(baseDir) + get().runSetup() }, runSetup: async () => { diff --git a/src/shared/types/electron.d.ts b/src/shared/types/electron.d.ts index d80163c..9a13bd3 100644 --- a/src/shared/types/electron.d.ts +++ b/src/shared/types/electron.d.ts @@ -27,14 +27,14 @@ declare global { deleteDirectory: (dirPath: string) => Promise<{ success: boolean; error?: string }> } settings: { - get: () => Promise<{ modelsDir: string; workspaceDir: string }> - set: (patch: { modelsDir?: string; workspaceDir?: string }) => Promise<{ modelsDir: string; workspaceDir: string }> + get: () => Promise<{ modelsDir: string; workspaceDir: string; extensionsDir: string }> + set: (patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }) => Promise<{ modelsDir: string; workspaceDir: string; extensionsDir: string }> } cache: { clear: () => Promise<{ success: boolean; error?: string }> } api: { - updatePaths: (patch: { modelsDir?: string; workspaceDir?: string }) => Promise<{ success: boolean; error?: string }> + updatePaths: (patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }) => Promise<{ success: boolean; error?: string }> } model: { export: (args: { outputUrl: string; format: string }) => Promise<{ success: boolean; error?: string }> @@ -67,14 +67,15 @@ declare global { deleteJob: (collection: string, filename: string) => Promise } setup: { - check: () => Promise<{ needed: boolean }> - run: () => Promise<{ success: boolean; error?: string }> - onProgress: (cb: (data: { step: string; percent: number; currentPackage?: string }) => void) => void - offProgress: () => void - onComplete: (cb: () => void) => void - offComplete: () => void - onError: (cb: (data: { message: string }) => void) => void - offError: () => void + check: () => Promise<{ needed: boolean; defaultDataDir: string }> + run: () => Promise<{ success: boolean; error?: string }> + saveDataDir: (baseDir: string) => Promise + onProgress: (cb: (data: { step: string; percent: number; currentPackage?: string }) => void) => void + offProgress: () => void + onComplete: (cb: () => void) => void + offComplete: () => void + onError: (cb: (data: { message: string }) => void) => void + offError: () => void } extensions: { list: () => Promise Date: Fri, 20 Mar 2026 09:34:00 +0100 Subject: [PATCH 02/20] feat(setup): add dependencies custom folder --- electron/main/ipc-handlers.ts | 7 ++++--- electron/main/python-bridge.ts | 7 +++++++ electron/main/python-setup.ts | 26 +++++++++++++++++++++----- electron/main/settings-store.ts | 14 ++++++++------ 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 6358399..7e1a5b9 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -41,9 +41,10 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe ipcMain.handle('setup:saveDataDir', async (_event, { baseDir }: { baseDir: string }) => { const userData = app.getPath('userData') setSettings(userData, { - modelsDir: join(baseDir, 'models'), - workspaceDir: join(baseDir, 'workspace'), - extensionsDir: join(baseDir, 'extensions'), + modelsDir: join(baseDir, 'models'), + workspaceDir: join(baseDir, 'workspace'), + extensionsDir: join(baseDir, 'extensions'), + dependenciesDir: join(baseDir, 'dependencies'), }) }) diff --git a/electron/main/python-bridge.ts b/electron/main/python-bridge.ts index e6d0801..c26aa06 100644 --- a/electron/main/python-bridge.ts +++ b/electron/main/python-bridge.ts @@ -55,6 +55,7 @@ export class PythonBridge { env: { ...process.env, PYTHONUNBUFFERED: '1', + PYTHONPATH: this.resolveDependenciesDir(), MODELS_DIR: this.resolveModelsDir(), WORKSPACE_DIR: this.resolveWorkspaceDir(), EXTENSIONS_DIR: this.resolveExtensionsDir(), @@ -234,4 +235,10 @@ export class PythonBridge { return s.extensionsDir } + private resolveDependenciesDir(): string { + const s = getSettings(app.getPath('userData')) + mkdirSync(s.dependenciesDir, { recursive: true }) + return s.dependenciesDir + } + } diff --git a/electron/main/python-setup.ts b/electron/main/python-setup.ts index 09c0fd4..cd0d7ee 100644 --- a/electron/main/python-setup.ts +++ b/electron/main/python-setup.ts @@ -1,8 +1,9 @@ import { BrowserWindow, app } from 'electron' -import { existsSync, readFileSync, writeFileSync, readdirSync } from 'fs' +import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs' import { join } from 'path' import { spawn, execSync } from 'child_process' import { createHash } from 'crypto' +import { getSettings } from './settings-store' const SETUP_VERSION = 2 const TOTAL_PACKAGES = 20 @@ -29,6 +30,11 @@ function hashRequirements(): string { // ─── Public helpers ────────────────────────────────────────────────────────── +function areSitePackagesInstalled(): boolean { + const { dependenciesDir } = getSettings(app.getPath('userData')) + return existsSync(join(dependenciesDir, 'uvicorn')) +} + export function checkSetupNeeded(userData: string): boolean { const jsonPath = join(userData, 'python_setup.json') if (!existsSync(jsonPath)) return true @@ -43,6 +49,8 @@ export function checkSetupNeeded(userData: string): boolean { if (process.platform !== 'win32' && app.isPackaged) { if (!existsSync(join(userData, 'venv', 'bin', 'python'))) return true } + // Verify packages are actually present (guards against reinstall without uninstall) + if (!areSitePackagesInstalled()) return true return false } @@ -73,7 +81,7 @@ export function getEmbeddedPythonExe(): string { return join(dir, 'bin', 'python3') } -function enableSitePackages(pythonDir: string, win: BrowserWindow): void { +function enableSitePackages(pythonDir: string, dependenciesDir: string, win: BrowserWindow): void { win.webContents.send('setup:progress', { step: 'enabling-site', percent: 5 }) const files = readdirSync(pythonDir) as string[] const pthFile = files.find((f) => f.match(/^python\d+\._pth$/)) @@ -87,6 +95,11 @@ function enableSitePackages(pythonDir: string, win: BrowserWindow): void { if (!content.includes('Lib\\site-packages')) { content = content.trimEnd() + '\nLib\\site-packages\n' } + // Add the user's dependencies dir so Python finds packages installed there. + // PYTHONPATH is ignored when a ._pth file is present (Python embed behavior). + if (!content.includes(dependenciesDir)) { + content = content.trimEnd() + `\n${dependenciesDir}\n` + } writeFileSync(pthPath, content, 'utf-8') console.log('[PythonSetup] Enabled site-packages in', pthFile) } @@ -116,11 +129,13 @@ function installRequirements( requirementsPath: string, win: BrowserWindow ): Promise { + const dependenciesDir = getSettings(app.getPath('userData')).dependenciesDir + mkdirSync(dependenciesDir, { recursive: true }) return new Promise((resolve, reject) => { - console.log('[PythonSetup] Installing requirements from', requirementsPath) + console.log('[PythonSetup] Installing requirements into', dependenciesDir) const proc = spawn( pythonExe, - ['-m', 'pip', 'install', '-r', requirementsPath, '--no-warn-script-location', '--progress-bar', 'off'], + ['-m', 'pip', 'install', '-r', requirementsPath, '--target', dependenciesDir, '--no-warn-script-location', '--progress-bar', 'off'], { stdio: ['ignore', 'pipe', 'pipe'] } ) let packagesInstalled = 0 @@ -213,7 +228,8 @@ export async function runFullSetup(win: BrowserWindow, userData: string): Promis ? process.resourcesPath : join(app.getAppPath(), 'resources') - enableSitePackages(pythonDir, win) + const dependenciesDir = getSettings(app.getPath('userData')).dependenciesDir + enableSitePackages(pythonDir, dependenciesDir, win) await installPip(pythonExe, resourcesPath, win) await installRequirements(pythonExe, requirementsPath, win) } else if (app.isPackaged) { diff --git a/electron/main/settings-store.ts b/electron/main/settings-store.ts index c12fb7e..177eea1 100644 --- a/electron/main/settings-store.ts +++ b/electron/main/settings-store.ts @@ -2,9 +2,10 @@ import { join } from 'path' import { readFileSync, writeFileSync, existsSync } from 'fs' export interface AppSettings { - modelsDir: string - workspaceDir: string - extensionsDir: string + modelsDir: string + workspaceDir: string + extensionsDir: string + dependenciesDir: string } function settingsPath(userData: string): string { @@ -13,9 +14,10 @@ function settingsPath(userData: string): string { export function getSettings(userData: string): AppSettings { const defaults: AppSettings = { - modelsDir: join(userData, 'models'), - workspaceDir: join(userData, 'workspace'), - extensionsDir: join(userData, 'extensions'), + modelsDir: join(userData, 'models'), + workspaceDir: join(userData, 'workspace'), + extensionsDir: join(userData, 'extensions'), + dependenciesDir: join(userData, 'dependencies'), } const file = settingsPath(userData) From 4c9a4d33ea1aef7cf2070df5a8c98bf5ba7388db Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 20 Mar 2026 12:26:55 +0100 Subject: [PATCH 03/20] error modal + fix dev mode to use embedded Python and data dir --- electron/main/index.ts | 5 ++- electron/main/ipc-handlers.ts | 3 +- electron/main/python-bridge.ts | 21 ++++----- electron/preload/index.ts | 6 ++- src/App.tsx | 14 +++++- src/shared/components/ui/ErrorModal.tsx | 57 +++++++++++++++++++++++++ src/shared/stores/appStore.ts | 22 +++++++--- src/shared/types/electron.d.ts | 2 + 8 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 src/shared/components/ui/ErrorModal.tsx diff --git a/electron/main/index.ts b/electron/main/index.ts index be04a0e..7e0fd66 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -48,10 +48,13 @@ app.setName('Modly') process.on('uncaughtException', (err) => { logger.error(`Uncaught exception: ${err.stack ?? err.message}`) + mainWindow?.webContents.send('app:error', err.stack ?? err.message) }) process.on('unhandledRejection', (reason) => { - logger.error(`Unhandled rejection: ${String(reason)}`) + const msg = String(reason) + logger.error(`Unhandled rejection: ${msg}`) + mainWindow?.webContents.send('app:error', msg) }) app.whenReady().then(async () => { diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 7e1a5b9..5aec759 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -32,9 +32,8 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe // Setup handlers — skipped in dev (uses .venv instead of python-embed) ipcMain.handle('setup:check', async () => { - const defaultDataDir = join(app.getPath('documents'), 'LocalMeshy') - if (!app.isPackaged) return { needed: false, defaultDataDir } const userData = app.getPath('userData') + const defaultDataDir = join(app.getPath('documents'), 'Modly') return { needed: checkSetupNeeded(userData), defaultDataDir } }) diff --git a/electron/main/python-bridge.ts b/electron/main/python-bridge.ts index c26aa06..88b097b 100644 --- a/electron/main/python-bridge.ts +++ b/electron/main/python-bridge.ts @@ -186,19 +186,14 @@ export class PythonBridge { : join(app.getAppPath(), 'resources') const userData = app.getPath('userData') - const candidates = app.isPackaged - ? [ - join(resourcesPath, 'python-embed', 'python.exe'), // Windows embeddable - join(userData, 'venv', 'bin', 'python'), // Linux/macOS venv (packaged) - 'python3', - 'python', - ] - : [ - join(apiDir, '.venv', 'Scripts', 'python.exe'), // Windows venv (dev) - join(apiDir, '.venv', 'bin', 'python'), // Unix/Mac venv (dev) - 'python', - 'python3', - ] + const candidates = [ + join(resourcesPath, 'python-embed', 'python.exe'), // Windows embedded (dev + packaged) + join(userData, 'venv', 'bin', 'python'), // Linux/macOS venv + join(apiDir, '.venv', 'Scripts', 'python.exe'), // legacy dev fallback + join(apiDir, '.venv', 'bin', 'python'), // legacy dev fallback + 'python3', + 'python', + ] for (const candidate of candidates) { if (existsSync(candidate)) { diff --git a/electron/preload/index.ts b/electron/preload/index.ts index b15dc09..7ada1e0 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -79,7 +79,11 @@ contextBridge.exposeInMainWorld('electron', { // App metadata app: { info: (): Promise<{ version: string; userData: string; modelsDir: string; apiUrl: string }> => - ipcRenderer.invoke('app:info') + ipcRenderer.invoke('app:info'), + onError: (cb: (message: string) => void) => { + ipcRenderer.on('app:error', (_event, message) => cb(message)) + }, + offError: () => ipcRenderer.removeAllListeners('app:error'), }, // Logging diff --git a/src/App.tsx b/src/App.tsx index ddd5e0f..13e0ca2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import { useAppStore } from '@shared/stores/appStore' import FirstRunSetup from '@areas/setup/FirstRunSetup' import MainLayout from '@shared/components/layout/MainLayout' import { UpdateModal } from '@shared/components/ui/UpdateModal' +import { ErrorModal } from '@shared/components/ui/ErrorModal' function compareSemver(a: string, b: string): number { const pa = a.replace(/^v/, '').split('.').map(Number) @@ -15,12 +16,15 @@ function compareSemver(a: string, b: string): number { } export default function App(): JSX.Element { - const { checkSetup, setupStatus, initApp, backendStatus } = useAppStore() + const { checkSetup, setupStatus, initApp, backendStatus, showError } = useAppStore() const [updateVersion, setUpdateVersion] = useState(null) const [currentVersion, setCurrentVersion] = useState('') useEffect(() => { checkSetup() + window.electron.app.onError((message) => showError(message)) + +return () => { window.electron.app.offError() } }, []) useEffect(() => { @@ -51,7 +55,13 @@ export default function App(): JSX.Element { onDismiss={() => setUpdateVersion(null)} /> )} + + + ) + return ( + <> + + ) - return } diff --git a/src/shared/components/ui/ErrorModal.tsx b/src/shared/components/ui/ErrorModal.tsx new file mode 100644 index 0000000..164ed8d --- /dev/null +++ b/src/shared/components/ui/ErrorModal.tsx @@ -0,0 +1,57 @@ +import { useState } from 'react' +import { useAppStore } from '@shared/stores/appStore' + +export function ErrorModal(): JSX.Element | null { + const { errorModal, hideError } = useAppStore() + const [copied, setCopied] = useState(false) + + if (!errorModal) return null + + const handleCopy = () => { + navigator.clipboard.writeText(errorModal).then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }) + } + + return ( +
+
+ {/* Header */} +
+
+ + + + + +
+ An error occurred +
+ + {/* Error message — selectable */} +
+
+            {errorModal}
+          
+
+ + {/* Actions */} +
+ + +
+
+
+ ) +} diff --git a/src/shared/stores/appStore.ts b/src/shared/stores/appStore.ts index 8d35d42..51edd17 100644 --- a/src/shared/stores/appStore.ts +++ b/src/shared/stores/appStore.ts @@ -89,6 +89,11 @@ interface AppState { runSetup: () => Promise saveDataDir: (baseDir: string) => Promise + // Error modal + errorModal: string | null + showError: (message: string) => void + hideError: () => void + // Actions initApp: () => Promise setCurrentJob: (job: GenerationJob | null) => void @@ -140,6 +145,10 @@ export const useAppStore = create()( window.electron.setup.run() }, + errorModal: null, + showError: (message) => set({ errorModal: message }), + hideError: () => set({ errorModal: null }), + currentJob: null, selectedImagePath: null, setSelectedImagePath: (path) => set({ selectedImagePath: path }), @@ -157,8 +166,10 @@ export const useAppStore = create()( set({ backendStatus: 'starting', backendError: null }) window.electron.python.offCrashed() - window.electron.python.onCrashed(() => { - set({ backendStatus: 'error', apiUrl: '', backendError: 'FastAPI crashed unexpectedly' }) + window.electron.python.onCrashed(({ code }) => { + const msg = `FastAPI process crashed unexpectedly (exit code: ${code ?? 'unknown'})` + set({ backendStatus: 'error', apiUrl: '', backendError: msg }) + get().showError(msg) }) try { @@ -167,10 +178,9 @@ export const useAppStore = create()( const { apiUrl } = await window.electron.app.info() set({ backendStatus: 'ready', apiUrl }) } catch (err) { - set({ - backendStatus: 'error', - backendError: err instanceof Error ? err.message : String(err), - }) + const msg = err instanceof Error ? err.message : String(err) + set({ backendStatus: 'error', backendError: msg }) + get().showError(msg) } }, diff --git a/src/shared/types/electron.d.ts b/src/shared/types/electron.d.ts index 9a13bd3..d54d611 100644 --- a/src/shared/types/electron.d.ts +++ b/src/shared/types/electron.d.ts @@ -52,6 +52,8 @@ declare global { modelsDir: string apiUrl: string }> + onError: (cb: (message: string) => void) => void + offError: () => void } log: { error: (message: string) => void From ffd51f04391e3883a1cefe1b9aac39b1d6514adf Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 20 Mar 2026 13:58:26 +0100 Subject: [PATCH 04/20] feat(setting): add session-based log rotation and logs viewer in settings --- electron/main/index.ts | 3 +- electron/main/ipc-handlers.ts | 29 +++- electron/main/logger.ts | 48 ++++-- electron/preload/index.ts | 2 + src/areas/settings/SettingsPage.tsx | 17 ++- src/areas/settings/components/LogsSection.tsx | 140 ++++++++++++++++++ src/shared/types/electron.d.ts | 2 + 7 files changed, 224 insertions(+), 17 deletions(-) create mode 100644 src/areas/settings/components/LogsSection.tsx diff --git a/electron/main/index.ts b/electron/main/index.ts index 7e0fd66..1154228 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -3,7 +3,7 @@ import { join } from 'path' import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { setupIpcHandlers } from './ipc-handlers' import { PythonBridge } from './python-bridge' -import { logger } from './logger' +import { logger, archiveCurrentSession } from './logger' let mainWindow: BrowserWindow | null = null let pythonBridge: PythonBridge | null = null @@ -58,6 +58,7 @@ process.on('unhandledRejection', (reason) => { }) app.whenReady().then(async () => { + archiveCurrentSession() logger.info(`App started — version ${app.getVersion()}`) electronApp.setAppUserModelId('com.modly.app') diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 5aec759..d43f146 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -1,7 +1,7 @@ import { ipcMain, BrowserWindow, dialog, app } from 'electron' import { join } from 'path' import { rm as rmAsync, readFile, writeFile, mkdir, readdir, rename, cp } from 'fs/promises' -import { existsSync } from 'fs' +import { existsSync, readdirSync, statSync } from 'fs' import axios from 'axios' import tar from 'tar' import { PythonBridge, API_BASE_URL } from './python-bridge' @@ -20,6 +20,33 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe // Logging from renderer ipcMain.on('log:error', (_event, message: string) => logger.error(`[Renderer] ${message}`)) ipcMain.handle('log:getPath', () => join(app.getPath('userData'), 'logs', 'modly.log')) + ipcMain.handle('log:readAll', async (_event, session?: string) => { + const logsDir = join(app.getPath('userData'), 'logs') + const dir = session ? join(logsDir, 'sessions', session) : logsDir + const files = ['modly.log', 'errors.log', 'runtime.log'] + const result: Record = {} + for (const file of files) { + try { + const filePath = join(dir, file) + result[file] = existsSync(filePath) ? await readFile(filePath, 'utf-8') : '' + } catch { + result[file] = '' + } + } + return result + }) + ipcMain.handle('log:listSessions', () => { + const sessionsDir = join(app.getPath('userData'), 'logs', 'sessions') + if (!existsSync(sessionsDir)) return [] + try { + return readdirSync(sessionsDir) + .filter(f => statSync(join(sessionsDir, f)).isDirectory()) + .sort() + .reverse() + } catch { + return [] + } + }) // Window controls (frameless window) ipcMain.on('window:minimize', () => getWindow()?.minimize()) diff --git a/electron/main/logger.ts b/electron/main/logger.ts index 09efd8b..c5da2ea 100644 --- a/electron/main/logger.ts +++ b/electron/main/logger.ts @@ -1,8 +1,9 @@ import { app } from 'electron' -import { appendFileSync, mkdirSync, existsSync, statSync, renameSync } from 'fs' +import { appendFileSync, mkdirSync, existsSync, readdirSync, statSync, renameSync, rmSync } from 'fs' import { join } from 'path' -const MAX_SIZE_BYTES = 5 * 1024 * 1024 // 5 MB +const MAX_SESSIONS = 10 +const LOG_FILES = ['modly.log', 'errors.log', 'runtime.log'] function getLogsDir(): string { const logsDir = join(app.getPath('userData'), 'logs') @@ -10,19 +11,9 @@ function getLogsDir(): string { return logsDir } -function rotate(logPath: string): void { +function writeTo(filename: string, logLine: string): void { try { - if (existsSync(logPath) && statSync(logPath).size > MAX_SIZE_BYTES) { - renameSync(logPath, logPath.replace('.log', '.old.log')) - } - } catch {} -} - -function writeTo(filename: string, line: string): void { - try { - const logPath = join(getLogsDir(), filename) - rotate(logPath) - appendFileSync(logPath, line, 'utf-8') + appendFileSync(join(getLogsDir(), filename), logLine, 'utf-8') } catch {} } @@ -30,6 +21,35 @@ function line(level: string, message: string): string { return `[${new Date().toISOString()}] [${level}] ${message}\n` } +export function archiveCurrentSession(): void { + const logsDir = getLogsDir() + const hasLogs = LOG_FILES.some(f => existsSync(join(logsDir, f))) + if (!hasLogs) return + + const timestamp = new Date().toISOString().replace(/:/g, '-').slice(0, 19) + const sessionsDir = join(logsDir, 'sessions') + const sessionDir = join(sessionsDir, timestamp) + mkdirSync(sessionDir, { recursive: true }) + + for (const file of LOG_FILES) { + const src = join(logsDir, file) + if (existsSync(src)) { + try { renameSync(src, join(sessionDir, file)) } catch {} + } + } + + // Keep only last MAX_SESSIONS + try { + const sessions = readdirSync(sessionsDir) + .filter(f => statSync(join(sessionsDir, f)).isDirectory()) + .sort() + .reverse() + for (const old of sessions.slice(MAX_SESSIONS)) { + rmSync(join(sessionsDir, old), { recursive: true, force: true }) + } + } catch {} +} + export const logger = { info: (msg: string) => { console.log(msg); writeTo('modly.log', line('INFO', msg)) }, warn: (msg: string) => { console.warn(msg); writeTo('modly.log', line('WARN', msg)) }, diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 7ada1e0..d584d6c 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -90,6 +90,8 @@ contextBridge.exposeInMainWorld('electron', { log: { error: (message: string) => ipcRenderer.send('log:error', message), getPath: (): Promise => ipcRenderer.invoke('log:getPath'), + readAll: (session?: string): Promise> => ipcRenderer.invoke('log:readAll', session), + listSessions: (): Promise => ipcRenderer.invoke('log:listSessions'), }, // Workspace filesystem-based persistence diff --git a/src/areas/settings/SettingsPage.tsx b/src/areas/settings/SettingsPage.tsx index 24a973f..7c9952d 100644 --- a/src/areas/settings/SettingsPage.tsx +++ b/src/areas/settings/SettingsPage.tsx @@ -1,8 +1,9 @@ import { useState } from 'react' import { StorageSection } from './components/StorageSection' import { AboutSection } from './components/AboutSection' +import { LogsSection } from './components/LogsSection' -type Section = 'storage' | 'about' +type Section = 'storage' | 'logs' | 'about' const SECTIONS: { id: Section; label: string; icon: JSX.Element }[] = [ { @@ -16,6 +17,19 @@ const SECTIONS: { id: Section; label: string; icon: JSX.Element }[] = [ ) }, + { + id: 'logs', + label: 'Logs', + icon: ( + + + + + + + + ) + }, { id: 'about', label: 'About', @@ -61,6 +75,7 @@ export default function SettingsPage(): JSX.Element {
{section === 'storage' && } + {section === 'logs' && } {section === 'about' && }
diff --git a/src/areas/settings/components/LogsSection.tsx b/src/areas/settings/components/LogsSection.tsx new file mode 100644 index 0000000..22fdbab --- /dev/null +++ b/src/areas/settings/components/LogsSection.tsx @@ -0,0 +1,140 @@ +import { useEffect, useState, useCallback } from 'react' + +const LOG_FILES = [ + { id: 'errors.log', label: 'Errors', description: 'All errors from Electron and Python' }, + { id: 'runtime.log', label: 'Runtime', description: 'FastAPI / Python output' }, + { id: 'modly.log', label: 'App', description: 'General Electron logs' }, +] + +function formatSession(id: string): string { + // id format: 2026-03-20T10-23-45 + try { + const iso = id.replace(/T(\d{2})-(\d{2})-(\d{2})$/, 'T$1:$2:$3') + const d = new Date(iso) + return d.toLocaleString(undefined, { dateStyle: 'medium', timeStyle: 'short' }) + } catch { + return id + } +} + +export function LogsSection(): JSX.Element { + const [sessions, setSessions] = useState([]) + const [activeSession, setActiveSession] = useState(null) // null = current + const [activeFile, setActiveFile] = useState('errors.log') + const [logs, setLogs] = useState>({}) + const [loading, setLoading] = useState(true) + const [copied, setCopied] = useState(false) + + const loadSessions = useCallback(async () => { + const list = await window.electron.log.listSessions() + setSessions(list) + }, []) + + const loadLogs = useCallback(async (session: string | null) => { + setLoading(true) + const result = await window.electron.log.readAll(session ?? undefined) + setLogs(result) + setLoading(false) + }, []) + + useEffect(() => { + loadSessions() + loadLogs(null) + }, [loadSessions, loadLogs]) + + const handleSessionChange = (value: string) => { + const session = value === 'current' ? null : value + setActiveSession(session) + loadLogs(session) + } + + const handleRefresh = () => { + loadSessions() + loadLogs(activeSession) + } + + const content = logs[activeFile] ?? '' + + const handleCopy = () => { + navigator.clipboard.writeText(content).then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2000) + }) + } + + return ( +
+
+

Logs

+

Application log files — share these when reporting issues.

+
+ + {/* Session selector */} +
+ + +
+ + {/* File tabs */} +
+ {LOG_FILES.map((f) => ( + + ))} + +
+ + +
+
+ + {/* Log content */} + {loading ? ( +
Loading…
+ ) : content ? ( +
+          {content}
+        
+ ) : ( +
+ No entries in {activeFile} +
+ )} +
+ ) +} diff --git a/src/shared/types/electron.d.ts b/src/shared/types/electron.d.ts index d54d611..f05e772 100644 --- a/src/shared/types/electron.d.ts +++ b/src/shared/types/electron.d.ts @@ -58,6 +58,8 @@ declare global { log: { error: (message: string) => void getPath: () => Promise + readAll: (session?: string) => Promise> + listSessions: () => Promise } workspace: { listCollections: () => Promise From 02b699b53994bc5f3f245cc3828ff600cd31e2c8 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 20 Mar 2026 14:17:20 +0100 Subject: [PATCH 05/20] fix(generate): disable generate without model, add image clear button --- src/areas/generate/GeneratePage.tsx | 9 ++++-- .../generate/components/GenerationOptions.tsx | 4 ++- src/areas/generate/components/ImageUpload.tsx | 28 +++++++++++++++---- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/areas/generate/GeneratePage.tsx b/src/areas/generate/GeneratePage.tsx index cf4cb5b..015c150 100644 --- a/src/areas/generate/GeneratePage.tsx +++ b/src/areas/generate/GeneratePage.tsx @@ -8,9 +8,13 @@ import Viewer3D from './components/Viewer3D' export default function GeneratePage(): JSX.Element { const selectedImagePath = useAppStore((s) => s.selectedImagePath) + const modelId = useAppStore((s) => s.generationOptions.modelId) const { currentJob, startGeneration } = useGeneration() const isGenerating = currentJob?.status === 'uploading' || currentJob?.status === 'generating' + const canGenerate = !!selectedImagePath && !!modelId && !isGenerating + const disabledReason = !selectedImagePath ? 'Select an image first' : !modelId ? 'No model selected — install one in the Models tab' : undefined + return ( <>
@@ -23,8 +27,9 @@ export default function GeneratePage(): JSX.Element { {/* Sticky bottom: Generate button */}
+ )} + ) : (
From 944459a4c1f0e50eead6d0eb82afafc2aa3ab837 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 20 Mar 2026 15:21:47 +0100 Subject: [PATCH 06/20] fix(generate): rename screenshot default filename --- src/areas/generate/components/Viewer3D.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/areas/generate/components/Viewer3D.tsx b/src/areas/generate/components/Viewer3D.tsx index 12d6f41..5122c86 100644 --- a/src/areas/generate/components/Viewer3D.tsx +++ b/src/areas/generate/components/Viewer3D.tsx @@ -224,7 +224,7 @@ export default function Viewer3D(): JSX.Element { const canvas = canvasRef.current if (!canvas) return const link = document.createElement('a') - link.download = `localmeshy-${Date.now()}.png` + link.download = `modly-${Date.now()}.png` link.href = canvas.toDataURL('image/png') link.click() } From d0697d74fb59ce85fc25a5258c5538df6dafdbee Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 20 Mar 2026 15:44:46 +0100 Subject: [PATCH 07/20] feat(models): show file count and current filename during model download --- api/routers/model.py | 2 +- electron/main/ipc-handlers.ts | 4 ++-- electron/main/model-downloader.ts | 17 ++++++++++++++-- electron/preload/index.ts | 2 +- src/areas/models/ModelsPage.tsx | 18 ++++------------- src/areas/models/components/ExtensionCard.tsx | 20 +++++++++++++------ src/shared/types/electron.d.ts | 2 +- 7 files changed, 38 insertions(+), 27 deletions(-) diff --git a/api/routers/model.py b/api/routers/model.py index 5a4d53d..2748405 100644 --- a/api/routers/model.py +++ b/api/routers/model.py @@ -105,7 +105,7 @@ def _dl(f=filename): # Reserve 1-95 for file downloads, leave 95-100 for finalisation pct = 1 + round((i + 1) / total * 94) - yield _fmt({"percent": pct, "file": filename}) + yield _fmt({"percent": pct, "file": filename, "fileIndex": i + 1, "totalFiles": total}) yield _fmt({"percent": 100, "status": "done"}) diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index d43f146..d4b9ab3 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -167,8 +167,8 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe ipcMain.handle('model:download', async (event, { repoId, modelId }: { repoId: string; modelId: string }) => { try { - await downloadModelFromHF(repoId, modelId, (pct) => { - event.sender.send('model:downloadProgress', { modelId, percent: pct }) + await downloadModelFromHF(repoId, modelId, (progress) => { + event.sender.send('model:downloadProgress', { modelId, ...progress }) }) return { success: true } } catch (err) { diff --git a/electron/main/model-downloader.ts b/electron/main/model-downloader.ts index 8a3f478..e3591a0 100644 --- a/electron/main/model-downloader.ts +++ b/electron/main/model-downloader.ts @@ -5,7 +5,14 @@ import { existsSync, readdirSync, statSync, readFileSync } from 'fs' import { join } from 'path' -export type ProgressCallback = (percent: number) => void +export interface DownloadProgress { + percent: number + file?: string + fileIndex?: number + totalFiles?: number + status?: string +} +export type ProgressCallback = (progress: DownloadProgress) => void const PYTHON_API_URL = process.env['PYTHON_API_URL'] ?? 'http://127.0.0.1:8765' @@ -126,7 +133,13 @@ export async function downloadModelFromHF( if (!line.startsWith('data: ')) continue try { const data = JSON.parse(line.slice(6)) - if (typeof data.percent === 'number') onProgress(data.percent) + if (typeof data.percent === 'number') onProgress({ + percent: data.percent, + file: data.file, + fileIndex: data.fileIndex, + totalFiles: data.totalFiles, + status: data.status, + }) if (data.error) throw new Error(`HF download error: ${data.error}`) } catch (e) { if (e instanceof Error && e.message.startsWith('HF download error:')) throw e diff --git a/electron/preload/index.ts b/electron/preload/index.ts index d584d6c..745d06d 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -70,7 +70,7 @@ contextBridge.exposeInMainWorld('electron', { isDownloaded: (modelId: string) => ipcRenderer.invoke('model:isDownloaded', modelId), download: (repoId: string, modelId: string) => ipcRenderer.invoke('model:download', { repoId, modelId }), delete: (modelId: string) => ipcRenderer.invoke('model:delete', modelId), - onProgress: (cb: (data: { modelId: string; percent: number }) => void) => { + onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => { ipcRenderer.on('model:downloadProgress', (_event, data) => cb(data)) }, offProgress: () => ipcRenderer.removeAllListeners('model:downloadProgress') diff --git a/src/areas/models/ModelsPage.tsx b/src/areas/models/ModelsPage.tsx index 65d118d..3ee66f5 100644 --- a/src/areas/models/ModelsPage.tsx +++ b/src/areas/models/ModelsPage.tsx @@ -6,7 +6,6 @@ import { ConfirmModal } from '@shared/components/ui' import { LocalModel } from './models' import { formatModelName } from './utils' import { ModelCard } from './components/ModelCard' -import { DownloadingCard } from './components/DownloadingCard' import { ExtensionCard, ExtensionVariant } from './components/ExtensionCard' // ─── Page ───────────────────────────────────────────────────────────────────── @@ -29,7 +28,7 @@ export default function ModelsPage(): JSX.Element { // HF models state const [models, setModels] = useState([]) - const [downloading, setDownloading] = useState>({}) + const [downloading, setDownloading] = useState>({}) const [deleteTarget, setDeleteTarget] = useState(null) const [deleteError, setDeleteError] = useState(null) const [uninstallTarget, setUninstallTarget] = useState(null) @@ -49,8 +48,8 @@ export default function ModelsPage(): JSX.Element { useEffect(() => { refresh() loadExtensions() - window.electron.model.onProgress(({ modelId: id, percent }) => { - setDownloading((prev) => ({ ...prev, [id]: percent })) + window.electron.model.onProgress(({ modelId: id, percent, file, fileIndex, totalFiles }) => { + setDownloading((prev) => ({ ...prev, [id]: { percent, file, fileIndex, totalFiles } })) if (percent === 100) { setDownloading((prev) => { const n = { ...prev }; delete n[id]; return n }) refresh() @@ -270,7 +269,7 @@ export default function ModelsPage(): JSX.Element { ext.models.map((v) => loadErrors[v.id]).find(Boolean) } onInstall={(variant: ExtensionVariant) => { - setDownloading((prev) => ({ ...prev, [variant.id]: 0 })) + setDownloading((prev) => ({ ...prev, [variant.id]: { percent: 0 } })) window.electron.model.download(variant.repoId, variant.id).then((result) => { if (!result.success) { setDownloading((prev) => { const n = { ...prev }; delete n[variant.id]; return n }) @@ -284,15 +283,6 @@ export default function ModelsPage(): JSX.Element { )}
- {/* In-progress downloads */} - {inProgressIds.length > 0 && ( -
-

Downloading

- {inProgressIds.map((id) => ( - - ))} -
- )} {/* Empty state */} {models.length === 0 && inProgressIds.length === 0 && ( diff --git a/src/areas/models/components/ExtensionCard.tsx b/src/areas/models/components/ExtensionCard.tsx index d7f26c5..2133a88 100644 --- a/src/areas/models/components/ExtensionCard.tsx +++ b/src/areas/models/components/ExtensionCard.tsx @@ -5,7 +5,7 @@ export type { Extension, ExtensionVariant } interface Props { ext: Extension installedIds: string[] - downloading: Record + downloading: Record loadError?: string disabled?: boolean onInstall: (variant: ExtensionVariant) => void @@ -104,8 +104,12 @@ export function ExtensionCard({ ext, installedIds, downloading, loadError, disab
{ext.models.map((variant) => { const installed = installedIds.includes(variant.id) - const dlPercent = downloading[variant.id] - const isDownloading = dlPercent !== undefined + const dlInfo = downloading[variant.id] + const isDownloading = dlInfo !== undefined + const dlPercent = dlInfo?.percent ?? 0 + const dlFile = dlInfo?.file?.split('/').pop() + const dlFileIndex = dlInfo?.fileIndex + const dlTotalFiles = dlInfo?.totalFiles return (
@@ -124,10 +128,14 @@ export function ExtensionCard({ ext, installedIds, downloading, loadError, disab Ready
) : isDownloading ? ( -
+
- Downloading… - {dlPercent}% + + Downloading… {dlFile ?? ''} + + + {dlFileIndex && dlTotalFiles ? `${dlFileIndex}/${dlTotalFiles} · ${dlPercent}%` : `${dlPercent}%`} +
Promise download: (repoId: string, modelId: string) => Promise<{ success: boolean; error?: string }> delete: (modelId: string) => Promise<{ success: boolean; error?: string }> - onProgress: (cb: (data: { modelId: string; percent: number }) => void) => void + onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => void offProgress: () => void } app: { From 1107c606d3951550187c0a3b5085d7f7757e0a62 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Fri, 20 Mar 2026 22:43:42 +0100 Subject: [PATCH 08/20] fix(setup): isolate Python env using bundled python-build-standalone + venv --- electron/main/python-bridge.ts | 92 ++++---------- electron/main/python-setup.ts | 203 +++++++++++++------------------ package.json | 12 +- scripts/download-python-embed.js | 88 ++++---------- 4 files changed, 144 insertions(+), 251 deletions(-) diff --git a/electron/main/python-bridge.ts b/electron/main/python-bridge.ts index 88b097b..dde2d66 100644 --- a/electron/main/python-bridge.ts +++ b/electron/main/python-bridge.ts @@ -5,6 +5,7 @@ import { existsSync, mkdirSync } from 'fs' import axios from 'axios' import { getSettings } from './settings-store' import { logger } from './logger' +import { cleanPythonEnv, getVenvPythonExe } from './python-setup' const API_PORT = 8765 const API_HOST = '127.0.0.1' @@ -21,12 +22,8 @@ export class PythonBridge { } async start(): Promise { - // Already fully ready if (this.ready) return - - // Startup already in progress — wait for the same promise instead of spawning again if (this.startPromise) return this.startPromise - this.startPromise = this._start() try { await this.startPromise @@ -37,7 +34,6 @@ export class PythonBridge { private async _start(): Promise { if (this.process) { - // Process spawned but not ready yet (e.g. second concurrent call) — just wait await this.waitUntilReady() return } @@ -53,9 +49,9 @@ export class PythonBridge { this.process = spawn(pythonExecutable, ['-m', 'uvicorn', 'main:app', '--host', API_HOST, '--port', String(API_PORT)], { cwd: apiDir, env: { - ...process.env, + ...cleanPythonEnv(), PYTHONUNBUFFERED: '1', - PYTHONPATH: this.resolveDependenciesDir(), + // No PYTHONPATH needed — the venv's Python has its own isolated site-packages MODELS_DIR: this.resolveModelsDir(), WORKSPACE_DIR: this.resolveWorkspaceDir(), EXTENSIONS_DIR: this.resolveExtensionsDir(), @@ -83,20 +79,18 @@ export class PythonBridge { this.ready = false this.process = null if (wasReady) { - // Server crashed while running — notify the renderer so it stops making API calls this.getWindow()?.webContents.send('python:crashed', { code }) } }) await this.waitUntilReady() - } // ← end of _start() + } async stop(): Promise { if (!this.process) return const proc = this.process this.process = null this.ready = false - // On Windows, taskkill /T kills the process AND all its children if (process.platform === 'win32') { const { execSync } = require('child_process') try { execSync(`taskkill /PID ${proc.pid} /T /F`) } catch {} @@ -107,20 +101,13 @@ export class PythonBridge { } private emitTqdmLog(raw: string): void { - // Skip uvicorn HTTP access logs and Python INFO logger lines if (/INFO/.test(raw)) return - // Skip empty lines if (!raw.trim()) return this.getWindow()?.webContents.send('python:log', raw.trim()) } - isReady(): boolean { - return this.ready - } - - getPort(): number { - return API_PORT - } + isReady(): boolean { return this.ready } + getPort(): number { return API_PORT } private async killProcessOnPort(): Promise { const { execSync } = require('child_process') @@ -130,43 +117,29 @@ export class PythonBridge { return } - // Retry loop — after a kill the port may take a few ms to be released for (let attempt = 0; attempt < 3; attempt++) { let output = '' try { - // ":8765 " with trailing space avoids matching :87650, :87651, etc. - output = execSync( - `netstat -ano | findstr ":${API_PORT} "`, - { encoding: 'utf8', shell: true } - ) as string - } catch { - break // findstr returns exit code 1 when no match — port is free - } + output = execSync(`netstat -ano | findstr ":${API_PORT} "`, { encoding: 'utf8', shell: true }) as string + } catch { break } const pids = new Set() for (const line of output.split('\n')) { const match = line.trim().match(/\s+(\d+)$/) if (match && match[1] !== '0') pids.add(match[1]) } - if (pids.size === 0) break for (const pid of pids) { - try { - execSync(`taskkill /PID ${pid} /T /F`, { shell: true }) - console.log(`[PythonBridge] Killed process tree PID ${pid} on port ${API_PORT}`) - } catch {} + try { execSync(`taskkill /PID ${pid} /T /F`, { shell: true }) } catch {} } - await new Promise((r) => setTimeout(r, 300)) } } private async waitUntilReady(maxRetries = 180, delayMs = 500): Promise { for (let i = 0; i < maxRetries; i++) { - if (!this.process) { - throw new Error('FastAPI process exited unexpectedly during startup') - } + if (!this.process) throw new Error('FastAPI process exited unexpectedly during startup') try { await axios.get(`${API_BASE_URL}/health`, { timeout: 2000 }) this.ready = true @@ -180,35 +153,31 @@ export class PythonBridge { } private resolvePythonExecutable(): string { - const apiDir = this.resolveApiDir() - const resourcesPath = app.isPackaged - ? process.resourcesPath - : join(app.getAppPath(), 'resources') const userData = app.getPath('userData') + const apiDir = this.resolveApiDir() - const candidates = [ - join(resourcesPath, 'python-embed', 'python.exe'), // Windows embedded (dev + packaged) - join(userData, 'venv', 'bin', 'python'), // Linux/macOS venv - join(apiDir, '.venv', 'Scripts', 'python.exe'), // legacy dev fallback - join(apiDir, '.venv', 'bin', 'python'), // legacy dev fallback - 'python3', - 'python', - ] + // Primary: venv created during setup (bundled Python → isolated venv) + const venvPython = getVenvPythonExe(userData) + if (existsSync(venvPython)) return venvPython - for (const candidate of candidates) { - if (existsSync(candidate)) { - return candidate - } + // Dev fallback: local .venv in the api directory + const devCandidates = [ + join(apiDir, '.venv', 'Scripts', 'python.exe'), + join(apiDir, '.venv', 'bin', 'python'), + ] + for (const c of devCandidates) { + if (existsSync(c)) return c } - return process.platform === 'win32' ? 'python' : 'python3' + // Never fall back to bare 'python' on Windows — it would be the user's system Python + if (process.platform === 'win32') { + throw new Error('Python venv not found. Please restart the application to re-run setup.') + } + return 'python3' } private resolveApiDir(): string { - if (app.isPackaged) { - return join(process.resourcesPath, 'api') - } - // In dev, app.getAppPath() returns the desktop/ folder + if (app.isPackaged) return join(process.resourcesPath, 'api') return join(app.getAppPath(), 'api') } @@ -229,11 +198,4 @@ export class PythonBridge { mkdirSync(s.extensionsDir, { recursive: true }) return s.extensionsDir } - - private resolveDependenciesDir(): string { - const s = getSettings(app.getPath('userData')) - mkdirSync(s.dependenciesDir, { recursive: true }) - return s.dependenciesDir - } - } diff --git a/electron/main/python-setup.ts b/electron/main/python-setup.ts index cd0d7ee..197a001 100644 --- a/electron/main/python-setup.ts +++ b/electron/main/python-setup.ts @@ -1,12 +1,11 @@ import { BrowserWindow, app } from 'electron' -import { existsSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs' +import { existsSync, readFileSync, writeFileSync } from 'fs' import { join } from 'path' import { spawn, execSync } from 'child_process' import { createHash } from 'crypto' import { getSettings } from './settings-store' -const SETUP_VERSION = 2 -const TOTAL_PACKAGES = 20 +const SETUP_VERSION = 3 interface SetupJson { version: number @@ -28,13 +27,54 @@ function hashRequirements(): string { } } -// ─── Public helpers ────────────────────────────────────────────────────────── +// ─── Environment ───────────────────────────────────────────────────────────── + +/** + * Clean environment for spawning Python/pip. + * Strips vars that could redirect imports or installs to the user's system Python. + */ +export function cleanPythonEnv(): NodeJS.ProcessEnv { + const { + PYTHONHOME, PYTHONPATH, PYTHONSTARTUP, PYTHONUSERBASE, + PIP_USER, PIP_TARGET, PIP_PREFIX, PIP_REQUIRE_VIRTUALENV, + VIRTUAL_ENV, CONDA_PREFIX, + ...rest + } = process.env + void PYTHONHOME; void PYTHONPATH; void PYTHONSTARTUP; void PYTHONUSERBASE + void PIP_USER; void PIP_TARGET; void PIP_PREFIX; void PIP_REQUIRE_VIRTUALENV + void VIRTUAL_ENV; void CONDA_PREFIX + return rest +} + +// ─── Path helpers ───────────────────────────────────────────────────────────── + +export function getEmbeddedPythonDir(): string { + if (app.isPackaged) return join(process.resourcesPath, 'python-embed') + return join(app.getAppPath(), 'resources', 'python-embed') +} + +export function getEmbeddedPythonExe(): string { + const dir = getEmbeddedPythonDir() + return process.platform === 'win32' ? join(dir, 'python.exe') : join(dir, 'bin', 'python3') +} + +/** Venv lives inside dependenciesDir on Windows (user-configurable drive), userData on Linux. */ +export function getVenvDir(userData: string): string { + if (process.platform === 'win32') { + return join(getSettings(userData).dependenciesDir, 'venv') + } + return join(userData, 'venv') +} -function areSitePackagesInstalled(): boolean { - const { dependenciesDir } = getSettings(app.getPath('userData')) - return existsSync(join(dependenciesDir, 'uvicorn')) +export function getVenvPythonExe(userData: string): string { + const venvDir = getVenvDir(userData) + return process.platform === 'win32' + ? join(venvDir, 'Scripts', 'python.exe') + : join(venvDir, 'bin', 'python') } +// ─── Setup state ────────────────────────────────────────────────────────────── + export function checkSetupNeeded(userData: string): boolean { const jsonPath = join(userData, 'python_setup.json') if (!existsSync(jsonPath)) return true @@ -45,98 +85,53 @@ export function checkSetupNeeded(userData: string): boolean { } catch { return true } - // On Unix packaged: also verify the venv was created - if (process.platform !== 'win32' && app.isPackaged) { - if (!existsSync(join(userData, 'venv', 'bin', 'python'))) return true - } - // Verify packages are actually present (guards against reinstall without uninstall) - if (!areSitePackagesInstalled()) return true + if (!existsSync(getVenvPythonExe(userData))) return true return false } export function markSetupDone(userData: string): void { - const jsonPath = join(userData, 'python_setup.json') writeFileSync( - jsonPath, + join(userData, 'python_setup.json'), JSON.stringify({ version: SETUP_VERSION, requirementsHash: hashRequirements() }), 'utf-8' ) } -/** Path to the venv Python executable created during setup (packaged Unix). */ -export function getVenvPythonExe(userData: string): string { - return join(userData, 'venv', 'bin', 'python') -} - -// ─── Embedded Python helpers (all platforms) ───────────────────────────────── - -export function getEmbeddedPythonDir(): string { - if (app.isPackaged) return join(process.resourcesPath, 'python-embed') - return join(app.getAppPath(), 'resources', 'python-embed') -} - -export function getEmbeddedPythonExe(): string { - const dir = getEmbeddedPythonDir() - if (process.platform === 'win32') return join(dir, 'python.exe') - return join(dir, 'bin', 'python3') -} +// ─── Setup steps ───────────────────────────────────────────────────────────── -function enableSitePackages(pythonDir: string, dependenciesDir: string, win: BrowserWindow): void { - win.webContents.send('setup:progress', { step: 'enabling-site', percent: 5 }) - const files = readdirSync(pythonDir) as string[] - const pthFile = files.find((f) => f.match(/^python\d+\._pth$/)) - if (!pthFile) { - console.warn('[PythonSetup] No ._pth file found in', pythonDir) - return - } - const pthPath = join(pythonDir, pthFile) - let content = readFileSync(pthPath, 'utf-8') - content = content.replace(/^#import site/m, 'import site') - if (!content.includes('Lib\\site-packages')) { - content = content.trimEnd() + '\nLib\\site-packages\n' - } - // Add the user's dependencies dir so Python finds packages installed there. - // PYTHONPATH is ignored when a ._pth file is present (Python embed behavior). - if (!content.includes(dependenciesDir)) { - content = content.trimEnd() + `\n${dependenciesDir}\n` - } - writeFileSync(pthPath, content, 'utf-8') - console.log('[PythonSetup] Enabled site-packages in', pthFile) -} - -function installPip(pythonExe: string, resourcesPath: string, win: BrowserWindow): Promise { +function createVenv(pythonExe: string, venvDir: string, win: BrowserWindow): Promise { return new Promise((resolve, reject) => { - win.webContents.send('setup:progress', { step: 'pip', percent: 10 }) - const getPipPath = join(resourcesPath, 'get-pip.py') - console.log('[PythonSetup] Installing pip from', getPipPath) - const proc = spawn(pythonExe, [getPipPath, '--no-warn-script-location'], { + win.webContents.send('setup:progress', { step: 'venv', percent: 5 }) + console.log('[PythonSetup] Creating venv at', venvDir) + const proc = spawn(pythonExe, ['-m', 'venv', venvDir], { stdio: ['ignore', 'pipe', 'pipe'], + env: cleanPythonEnv(), }) - proc.stdout?.on('data', (d: Buffer) => console.log('[pip install]', d.toString().trim())) - proc.stderr?.on('data', (d: Buffer) => console.error('[pip install]', d.toString().trim())) + proc.stdout?.on('data', (d: Buffer) => console.log('[venv]', d.toString().trim())) + proc.stderr?.on('data', (d: Buffer) => console.error('[venv]', d.toString().trim())) proc.on('close', (code) => { - win.webContents.send('setup:progress', { step: 'pip', percent: 20 }) - if (code === 0) resolve() - else reject(new Error(`get-pip.py exited with code ${code}`)) + if (code === 0) { + win.webContents.send('setup:progress', { step: 'venv', percent: 20 }) + resolve() + } else { + reject(new Error(`python -m venv exited with code ${code}`)) + } }) }) } -// ─── Shared helper ─────────────────────────────────────────────────────────── - function installRequirements( pythonExe: string, requirementsPath: string, win: BrowserWindow ): Promise { - const dependenciesDir = getSettings(app.getPath('userData')).dependenciesDir - mkdirSync(dependenciesDir, { recursive: true }) + const TOTAL_PACKAGES = 20 return new Promise((resolve, reject) => { - console.log('[PythonSetup] Installing requirements into', dependenciesDir) + console.log('[PythonSetup] Installing requirements with', pythonExe) const proc = spawn( pythonExe, - ['-m', 'pip', 'install', '-r', requirementsPath, '--target', dependenciesDir, '--no-warn-script-location', '--progress-bar', 'off'], - { stdio: ['ignore', 'pipe', 'pipe'] } + ['-m', 'pip', 'install', '-r', requirementsPath, '--no-warn-script-location', '--progress-bar', 'off'], + { stdio: ['ignore', 'pipe', 'pipe'], env: cleanPythonEnv() } ) let packagesInstalled = 0 const onLine = (line: string) => { @@ -175,7 +170,7 @@ function installRequirements( }) } -// ─── Unix helpers (venv) ───────────────────────────────────────────────────── +// ─── Unix dev helper ───────────────────────────────────────────────────────── function findSystemPython(): string { const candidates = ['python3.12', 'python3.11', 'python3.10', 'python3', 'python'] @@ -186,7 +181,7 @@ function findSystemPython(): string { console.log(`[PythonSetup] Found system Python: ${cmd} → ${out}`) return cmd } - } catch { /* not found, try next */ } + } catch { /* not found */ } } throw new Error( 'Python 3 not found on your system.\n' + @@ -196,58 +191,34 @@ function findSystemPython(): string { ) } -function createVenv(python3: string, venvDir: string, win: BrowserWindow): Promise { - return new Promise((resolve, reject) => { - win.webContents.send('setup:progress', { step: 'venv', percent: 10 }) - console.log('[PythonSetup] Creating venv at', venvDir) - const proc = spawn(python3, ['-m', 'venv', venvDir], { stdio: ['ignore', 'pipe', 'pipe'] }) - proc.stdout?.on('data', (d: Buffer) => console.log('[venv]', d.toString().trim())) - proc.stderr?.on('data', (d: Buffer) => console.error('[venv]', d.toString().trim())) - proc.on('close', (code) => { - if (code === 0) { - win.webContents.send('setup:progress', { step: 'venv', percent: 20 }) - resolve() - } else { - reject(new Error(`python3 -m venv exited with code ${code}`)) - } - }) - }) -} - -// ─── Public orchestrator ───────────────────────────────────────────────────── +// ─── Public orchestrator ────────────────────────────────────────────────────── export async function runFullSetup(win: BrowserWindow, userData: string): Promise { try { const requirementsPath = getRequirementsPath() + const venvDir = getVenvDir(userData) - if (process.platform === 'win32') { - // Windows: use embedded Python bundled with the app - const pythonDir = getEmbeddedPythonDir() + if (process.platform === 'win32' || app.isPackaged) { + // Packaged (all platforms) + Windows dev: use bundled python-build-standalone. + // python-build-standalone is a full Python install → venv module works natively, + // DLLs come from the installer so SAC doesn't block them. const pythonExe = getEmbeddedPythonExe() - const resourcesPath = app.isPackaged - ? process.resourcesPath - : join(app.getAppPath(), 'resources') - - const dependenciesDir = getSettings(app.getPath('userData')).dependenciesDir - enableSitePackages(pythonDir, dependenciesDir, win) - await installPip(pythonExe, resourcesPath, win) - await installRequirements(pythonExe, requirementsPath, win) - } else if (app.isPackaged) { - // Linux / macOS packaged: use bundled Python to create a venv in userData - // (resources dir may be read-only inside .app bundle) - win.webContents.send('setup:progress', { step: 'venv', percent: 5 }) - const python3 = getEmbeddedPythonExe() - const venvDir = join(userData, 'venv') - await createVenv(python3, venvDir, win) - const venvPython = join(venvDir, 'bin', 'python') + if (!existsSync(pythonExe)) { + throw new Error( + 'Bundled Python runtime not found.\n' + + 'Please reinstall the application.\n' + + `(expected: ${pythonExe})` + ) + } + await createVenv(pythonExe, venvDir, win) + const venvPython = getVenvPythonExe(userData) await installRequirements(venvPython, requirementsPath, win) } else { - // Linux / macOS dev: create a venv using the system Python - win.webContents.send('setup:progress', { step: 'python', percent: 5 }) + // Linux / macOS dev: use system Python + win.webContents.send('setup:progress', { step: 'venv', percent: 5 }) const python3 = findSystemPython() - const venvDir = join(userData, 'venv') await createVenv(python3, venvDir, win) - const venvPython = join(venvDir, 'bin', 'python') + const venvPython = getVenvPythonExe(userData) await installRequirements(venvPython, requirementsPath, win) } diff --git a/package.json b/package.json index 27de181..3dfd22e 100644 --- a/package.json +++ b/package.json @@ -73,10 +73,6 @@ { "from": "resources/python-embed", "to": "python-embed" - }, - { - "from": "resources/get-pip.py", - "to": "get-pip.py" } ] }, @@ -86,7 +82,13 @@ }, "linux": { "target": "AppImage", - "icon": "resources/icons/icon.png" + "icon": "resources/icons/icon.png", + "extraResources": [ + { + "from": "resources/python-embed", + "to": "python-embed" + } + ] } } } diff --git a/scripts/download-python-embed.js b/scripts/download-python-embed.js index 68f10df..25cbd01 100644 --- a/scripts/download-python-embed.js +++ b/scripts/download-python-embed.js @@ -7,34 +7,32 @@ const { execSync } = require('child_process') const RESOURCES_DIR = path.join(__dirname, '..', 'resources') const EMBED_DIR = path.join(RESOURCES_DIR, 'python-embed') -// ── Windows: official embeddable package ───────────────────────────────────── -const WIN_PYTHON_VERSION = '3.11.9' -const WIN_ZIP_URL = `https://www.python.org/ftp/python/${WIN_PYTHON_VERSION}/python-${WIN_PYTHON_VERSION}-embed-amd64.zip` -const WIN_GET_PIP_URL = 'https://bootstrap.pypa.io/get-pip.py' - -// ── Linux / macOS: python-build-standalone ─────────────────────────────────── -const PBS_PYTHON_VERSION = '3.11.9' +// python-build-standalone provides a full Python installation (includes venv + pip) +// Used for ALL platforms — consistent behavior, no stripped-down embed issues. +const PBS_VERSION = '3.11.9' const PBS_RELEASE = '20240726' function getPbsUrl() { const arch = process.arch === 'arm64' ? 'aarch64' : 'x86_64' - const triple = process.platform === 'darwin' - ? `${arch}-apple-darwin` - : `${arch}-unknown-linux-gnu` + const triple = process.platform === 'win32' + ? `${arch}-pc-windows-msvc` + : process.platform === 'darwin' + ? `${arch}-apple-darwin` + : `${arch}-unknown-linux-gnu` return ( `https://github.com/indygreg/python-build-standalone/releases/download/` + - `${PBS_RELEASE}/cpython-${PBS_PYTHON_VERSION}+${PBS_RELEASE}-${triple}-install_only.tar.gz` + `${PBS_RELEASE}/cpython-${PBS_VERSION}+${PBS_RELEASE}-${triple}-install_only.tar.gz` ) } -// ── Shared helpers ──────────────────────────────────────────────────────────── +// ── Helpers ─────────────────────────────────────────────────────────────────── function download(url, dest) { return new Promise((resolve, reject) => { console.log(`Downloading ${url} → ${dest}`) const file = fs.createWriteStream(dest) const request = (u) => { - https.get(u, (res) => { + https.get(u, { headers: { 'User-Agent': 'modly-build' } }, (res) => { if (res.statusCode === 301 || res.statusCode === 302) { request(res.headers.location) return @@ -64,23 +62,11 @@ function download(url, dest) { }) } -// ── Platform-specific extraction ────────────────────────────────────────────── - -function extractZip(zipPath, destDir) { - console.log(`Extracting ${zipPath} → ${destDir}`) - fs.mkdirSync(destDir, { recursive: true }) - execSync( - `powershell -NoProfile -Command "Expand-Archive -Path '${zipPath}' -DestinationPath '${destDir}' -Force"`, - { stdio: 'inherit' } - ) -} - function extractTar(tarPath, destDir) { console.log(`Extracting ${tarPath} → ${destDir}`) fs.mkdirSync(destDir, { recursive: true }) // --strip-components=1 removes the top-level "python/" directory from the archive execSync(`tar -xzf "${tarPath}" --strip-components=1 -C "${destDir}"`, { stdio: 'inherit' }) - // On macOS, remove quarantine attribute so Gatekeeper doesn't block execution if (process.platform === 'darwin') { try { execSync(`xattr -cr "${destDir}"`) } catch {} } @@ -91,49 +77,21 @@ function extractTar(tarPath, destDir) { async function main() { fs.mkdirSync(RESOURCES_DIR, { recursive: true }) - if (process.platform === 'win32') { - const pythonExe = path.join(EMBED_DIR, 'python.exe') - const getPipDest = path.join(RESOURCES_DIR, 'get-pip.py') - const zipTmp = path.join(RESOURCES_DIR, 'python-embed.zip') - - if (fs.existsSync(pythonExe) && fs.existsSync(getPipDest)) { - console.log('python-embed (Windows) already present, skipping.') - return - } - - if (!fs.existsSync(pythonExe)) { - await download(WIN_ZIP_URL, zipTmp) - extractZip(zipTmp, EMBED_DIR) - fs.unlinkSync(zipTmp) - console.log('Python embeddable extracted.') - } else { - console.log('python.exe already present, skipping ZIP download.') - } - - if (!fs.existsSync(getPipDest)) { - await download(WIN_GET_PIP_URL, getPipDest) - console.log('get-pip.py downloaded.') - } else { - console.log('get-pip.py already present.') - } - } else { - // Linux / macOS: python-build-standalone (already includes pip) - const pythonExe = path.join(EMBED_DIR, 'bin', 'python3') - - if (fs.existsSync(pythonExe)) { - console.log('python-embed (Unix) already present, skipping.') - return - } + const pythonExe = process.platform === 'win32' + ? path.join(EMBED_DIR, 'python.exe') + : path.join(EMBED_DIR, 'bin', 'python3') - const tarUrl = getPbsUrl() - const tarTmp = path.join(RESOURCES_DIR, 'python-embed.tar.gz') - await download(tarUrl, tarTmp) - extractTar(tarTmp, EMBED_DIR) - fs.unlinkSync(tarTmp) - console.log('Python standalone extracted.') + if (fs.existsSync(pythonExe)) { + console.log('python-embed already present, skipping.') + return } - console.log('Done. Resources ready.') + const tarUrl = getPbsUrl() + const tarTmp = path.join(RESOURCES_DIR, 'python-embed.tar.gz') + await download(tarUrl, tarTmp) + extractTar(tarTmp, EMBED_DIR) + fs.unlinkSync(tarTmp) + console.log('Done. Python standalone extracted.') } main().catch((err) => { From cbf2b898d3146dca3d16a9ac8de3433bcdc967a0 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sat, 21 Mar 2026 21:21:00 +0100 Subject: [PATCH 09/20] feat(structure): add auto-updater with patch version --- .github/workflows/release.yml | 54 +- electron/main/index.ts | 2 + electron/main/ipc-handlers.ts | 17 + electron/main/updater.ts | 43 + electron/preload/index.ts | 16 + package-lock.json | 1975 +++++++---------------- package.json | 10 +- src/App.tsx | 33 +- src/shared/components/layout/TopBar.tsx | 16 + src/shared/stores/appStore.ts | 7 + src/shared/types/electron.d.ts | 8 + 11 files changed, 726 insertions(+), 1455 deletions(-) create mode 100644 electron/main/updater.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index febcc61..95ec6d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,19 +28,25 @@ jobs: - name: Download Python embed run: node scripts/download-python-embed.js + - name: Create draft release + run: | + gh release create ${{ github.ref_name }} \ + --title "Modly Beta ${{ github.ref_name }}" \ + --generate-notes \ + --draft \ + --repo ${{ github.repository }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Build & Package (Windows) - run: npx electron-vite build && npx electron-builder --win --publish never + run: npx electron-vite build && npx electron-builder --win --publish always env: CSC_IDENTITY_AUTO_DISCOVERY: false - - - name: Upload Windows artifact - uses: actions/upload-artifact@v4 - with: - name: windows-installer - path: dist/*.exe + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # ─── Linux build ───────────────────────────────────────────────────────────── build-linux: + needs: build-windows runs-on: ubuntu-latest steps: - name: Checkout @@ -59,38 +65,6 @@ jobs: run: sudo apt-get install -y rpm - name: Build & Package (Linux) - run: npx electron-vite build && npx electron-builder --linux --publish never - - - name: Upload Linux artifact - uses: actions/upload-artifact@v4 - with: - name: linux-installer - path: dist/*.AppImage - - # ─── Create GitHub Release ─────────────────────────────────────────────────── - release: - needs: [build-windows, build-linux] - runs-on: ubuntu-latest - steps: - - name: Download Windows artifact - uses: actions/download-artifact@v4 - with: - name: windows-installer - path: artifacts/windows - - - name: Download Linux artifact - uses: actions/download-artifact@v4 - with: - name: linux-installer - path: artifacts/linux - - - name: Create GitHub Release - run: | - gh release create ${{ github.ref_name }} \ - artifacts/windows/*.exe \ - artifacts/linux/*.AppImage \ - --title "Modly Beta ${{ github.ref_name }}" \ - --generate-notes \ - --repo ${{ github.repository }} + run: npx electron-vite build && npx electron-builder --linux --publish always env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/electron/main/index.ts b/electron/main/index.ts index 1154228..653e747 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -4,6 +4,7 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils' import { setupIpcHandlers } from './ipc-handlers' import { PythonBridge } from './python-bridge' import { logger, archiveCurrentSession } from './logger' +import { initAutoUpdater } from './updater' let mainWindow: BrowserWindow | null = null let pythonBridge: PythonBridge | null = null @@ -73,6 +74,7 @@ app.whenReady().then(async () => { pythonBridge = new PythonBridge() pythonBridge.setWindowGetter(() => mainWindow) setupIpcHandlers(pythonBridge, () => mainWindow) + initAutoUpdater(() => mainWindow) createWindow() diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index d4b9ab3..6632f84 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -1,4 +1,5 @@ import { ipcMain, BrowserWindow, dialog, app } from 'electron' +import { autoUpdater } from 'electron-updater' import { join } from 'path' import { rm as rmAsync, readFile, writeFile, mkdir, readdir, rename, cp } from 'fs/promises' import { existsSync, readdirSync, statSync } from 'fs' @@ -544,6 +545,22 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe } }) + // Auto-updater + ipcMain.handle('updater:check', async () => { + if (!app.isPackaged) return { success: false } + try { + await autoUpdater.checkForUpdates() + return { success: true } + } catch (err) { + logger.error(`[updater:check] ${err}`) + return { success: false } + } + }) + + ipcMain.handle('updater:quitAndInstall', () => { + autoUpdater.quitAndInstall(false, true) + }) + // Update FastAPI paths at runtime (without restarting) ipcMain.handle('api:updatePaths', async (_event, patch: { modelsDir?: string; workspaceDir?: string; extensionsDir?: string }) => { try { diff --git a/electron/main/updater.ts b/electron/main/updater.ts new file mode 100644 index 0000000..262748b --- /dev/null +++ b/electron/main/updater.ts @@ -0,0 +1,43 @@ +import { app, BrowserWindow } from 'electron' +import { autoUpdater } from 'electron-updater' +import { logger } from './logger' + +type WindowGetter = () => BrowserWindow | null + +export function initAutoUpdater(getWindow: WindowGetter): void { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + autoUpdater.logger = logger as any + autoUpdater.autoDownload = false + autoUpdater.autoInstallOnAppQuit = true + + autoUpdater.on('update-available', (info) => { + const running = app.getVersion() + const incoming = info.version + const [rMaj, rMin] = running.split('.').map(Number) + const [iMaj, iMin] = incoming.split('.').map(Number) + const isPatch = rMaj === iMaj && rMin === iMin + + if (isPatch) { + logger.info(`[updater] Patch update ${incoming} available — downloading silently`) + autoUpdater.downloadUpdate().catch((err: Error) => { + logger.error(`[updater] Download failed: ${err.message}`) + }) + } else { + logger.info(`[updater] Major/minor update ${incoming} available — notifying renderer`) + getWindow()?.webContents.send('updater:major-minor-available', { version: incoming }) + } + }) + + autoUpdater.on('update-downloaded', (info) => { + logger.info(`[updater] Patch update ${info.version} downloaded — showing badge`) + getWindow()?.webContents.send('updater:patch-ready', { version: info.version }) + }) + + autoUpdater.on('update-not-available', () => { + logger.info('[updater] App is up to date') + }) + + autoUpdater.on('error', (err: Error) => { + logger.error(`[updater] Error: ${err.message}`) + }) +} diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 745d06d..d9d8aa7 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -148,6 +148,22 @@ contextBridge.exposeInMainWorld('electron', { offInstallProgress: () => ipcRenderer.removeAllListeners('extensions:installProgress'), }, + // Auto-updater + updater: { + check: (): Promise<{ success: boolean }> => + ipcRenderer.invoke('updater:check'), + quitAndInstall: (): Promise => + ipcRenderer.invoke('updater:quitAndInstall'), + onPatchReady: (cb: (data: { version: string }) => void) => { + ipcRenderer.on('updater:patch-ready', (_event, data) => cb(data)) + }, + offPatchReady: () => ipcRenderer.removeAllListeners('updater:patch-ready'), + onMajorMinorAvailable: (cb: (data: { version: string }) => void) => { + ipcRenderer.on('updater:major-minor-available', (_event, data) => cb(data)) + }, + offMajorMinorAvailable: () => ipcRenderer.removeAllListeners('updater:major-minor-available'), + }, + // First-run setup setup: { check: (): Promise<{ needed: boolean; defaultDataDir: string }> => diff --git a/package-lock.json b/package-lock.json index 9af5939..da58bda 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { - "name": "local-meshy", - "version": "0.1.0", + "name": "modly", + "version": "0.1.4", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "local-meshy", - "version": "0.1.0", + "name": "modly", + "version": "0.1.4", "dependencies": { "@electron-toolkit/utils": "^4.0.0", "@react-three/drei": "^9.120.0", "@react-three/fiber": "^8.17.10", "axios": "^1.7.9", + "electron-updater": "^6.8.3", "react": "^18.3.1", "react-dom": "^18.3.1", "tar": "^7.5.9", @@ -28,7 +29,7 @@ "autoprefixer": "^10.4.20", "cross-env": "^10.1.0", "electron": "^33.3.0", - "electron-builder": "^25.1.8", + "electron-builder": "^24.13.3", "electron-vite": "^2.3.0", "eslint": "^9.17.0", "postcss": "^8.4.49", @@ -340,6 +341,7 @@ "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.0", "ajv-keywords": "^3.4.1" @@ -365,6 +367,7 @@ "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", "dev": true, + "license": "MIT", "dependencies": { "commander": "^5.0.0", "glob": "^7.1.6", @@ -377,27 +380,23 @@ "node": ">=10.12.0" } }, - "node_modules/@electron/asar/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, "node_modules/@electron/asar/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/@electron/asar/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -426,10 +425,11 @@ } }, "node_modules/@electron/notarize": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", - "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.2.1.tgz", + "integrity": "sha512-aL+bFMIkpR0cmmj5Zgy0LMKEpgy43/hw5zadEArgmAMWWlKc5buwFvFT9G/o/YJkvXAJm5q3iuTuLaiaXW39sg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.1", @@ -444,6 +444,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, + "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -459,6 +460,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -471,15 +473,17 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, "node_modules/@electron/osx-sign": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.1.tgz", - "integrity": "sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.0.5.tgz", + "integrity": "sha512-k9ZzUQtamSoweGQDV2jILiRIHUu7lYlJ3c6IEmjv1hC17rclE+eb9U+f6UFlOOETo0JzY1HNlXy4YOlCvl+Lww==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "compare-version": "^0.1.2", "debug": "^4.3.4", @@ -501,6 +505,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -515,6 +520,7 @@ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8.0.0" }, @@ -527,6 +533,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -539,148 +546,55 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, - "node_modules/@electron/rebuild": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-3.6.1.tgz", - "integrity": "sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==", - "dev": true, - "dependencies": { - "@malept/cross-spawn-promise": "^2.0.0", - "chalk": "^4.0.0", - "debug": "^4.1.1", - "detect-libc": "^2.0.1", - "fs-extra": "^10.0.0", - "got": "^11.7.0", - "node-abi": "^3.45.0", - "node-api-version": "^0.2.0", - "node-gyp": "^9.0.0", - "ora": "^5.1.0", - "read-binary-file-arch": "^1.0.6", - "semver": "^7.3.5", - "tar": "^6.0.5", - "yargs": "^17.0.1" - }, - "bin": { - "electron-rebuild": "lib/cli.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@electron/rebuild/node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@electron/rebuild/node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/@electron/rebuild/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@electron/rebuild/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/rebuild/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "node_modules/@electron/universal": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.5.1.tgz", + "integrity": "sha512-kbgXxyEauPJiQQUNG2VgUeyfQNFk6hBF11ISN2PNI6agUgPl55pv4eQmaqHzTAzchBvqZ2tQuRVaPStGf0mxGw==", "dev": true, + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@electron/asar": "^3.2.1", + "@malept/cross-spawn-promise": "^1.1.0", + "debug": "^4.3.1", + "dir-compare": "^3.0.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@electron/rebuild/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" + "node": ">=8.6" } }, - "node_modules/@electron/rebuild/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/@electron/universal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.1.tgz", - "integrity": "sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==", + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { - "@electron/asar": "^3.2.7", - "@malept/cross-spawn-promise": "^2.0.0", - "debug": "^4.3.1", - "dir-compare": "^4.2.0", - "fs-extra": "^11.1.1", - "minimatch": "^9.0.3", - "plist": "^3.1.0" - }, - "engines": { - "node": ">=16.4" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/@electron/universal/node_modules/fs-extra": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", - "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, + "license": "MIT", "dependencies": { + "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" }, "engines": { - "node": ">=14.14" + "node": ">=10" } }, "node_modules/@electron/universal/node_modules/jsonfile": { @@ -688,6 +602,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -696,18 +611,16 @@ } }, "node_modules/@electron/universal/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/@electron/universal/node_modules/universalify": { @@ -715,6 +628,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -1147,12 +1061,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1222,12 +1130,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1284,12 +1186,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "dev": true - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1343,6 +1239,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -1360,6 +1257,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1372,6 +1270,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -1383,13 +1282,15 @@ "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -1403,12 +1304,13 @@ } }, "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1422,6 +1324,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -1499,9 +1402,9 @@ } }, "node_modules/@malept/cross-spawn-promise": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", - "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", "dev": true, "funding": [ { @@ -1513,11 +1416,12 @@ "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" } ], + "license": "Apache-2.0", "dependencies": { "cross-spawn": "^7.0.1" }, "engines": { - "node": ">= 12.13.0" + "node": ">= 10" } }, "node_modules/@malept/flatpak-bundler": { @@ -1525,6 +1429,7 @@ "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^9.0.0", @@ -1540,6 +1445,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, + "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -1555,6 +1461,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -1567,6 +1474,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -1622,50 +1530,12 @@ "node": ">= 8" } }, - "node_modules/@npmcli/fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", - "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", - "dev": true, - "dependencies": { - "@gar/promisify": "^1.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/@npmcli/fs/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", - "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", - "deprecated": "This functionality has been moved to @npmcli/fs", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -2218,6 +2088,7 @@ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10" } @@ -2280,10 +2151,11 @@ } }, "node_modules/@types/debug": { - "version": "4.1.12", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", - "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", "dev": true, + "license": "MIT", "dependencies": { "@types/ms": "*" } @@ -2304,6 +2176,7 @@ "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", "dev": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -2331,7 +2204,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "22.19.11", @@ -2351,6 +2225,7 @@ "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*", @@ -2429,6 +2304,7 @@ "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", "dev": true, + "license": "MIT", "optional": true }, "node_modules/@types/webxr": { @@ -2491,6 +2367,7 @@ "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" } @@ -2499,13 +2376,8 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.2.0.tgz", "integrity": "sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/acorn": { "version": "8.16.0", @@ -2529,37 +2401,16 @@ } }, "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", - "dev": true, - "engines": { - "node": ">= 14" - } - }, - "node_modules/agentkeepalive": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", - "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", - "dev": true, - "dependencies": { - "humanize-ms": "^1.2.1" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, + "license": "MIT", "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" + "debug": "4" }, "engines": { - "node": ">=8" + "node": ">= 6.0.0" } }, "node_modules/ajv": { @@ -2583,6 +2434,7 @@ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", "dev": true, + "license": "MIT", "peerDependencies": { "ajv": "^6.9.1" } @@ -2592,6 +2444,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2631,45 +2484,42 @@ } }, "node_modules/app-builder-bin": { - "version": "5.0.0-alpha.10", - "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-5.0.0-alpha.10.tgz", - "integrity": "sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" }, "node_modules/app-builder-lib": { - "version": "25.1.8", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-25.1.8.tgz", - "integrity": "sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-24.13.3.tgz", + "integrity": "sha512-FAzX6IBit2POXYGnTCT8YHFO/lr5AapAII6zzhQO3Rw4cEDOgK+t1xhLc5tNcKlicTHlo9zxIwnYCX9X2DLkig==", "dev": true, + "license": "MIT", "dependencies": { "@develar/schema-utils": "~2.6.5", - "@electron/notarize": "2.5.0", - "@electron/osx-sign": "1.3.1", - "@electron/rebuild": "3.6.1", - "@electron/universal": "2.0.1", + "@electron/notarize": "2.2.1", + "@electron/osx-sign": "1.0.5", + "@electron/universal": "1.5.1", "@malept/flatpak-bundler": "^0.4.0", "@types/fs-extra": "9.0.13", "async-exit-hook": "^2.0.1", "bluebird-lst": "^1.0.9", - "builder-util": "25.1.7", - "builder-util-runtime": "9.2.10", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", "chromium-pickle-js": "^0.2.0", - "config-file-ts": "0.2.8-rc1", "debug": "^4.3.4", - "dotenv": "^16.4.5", - "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", - "electron-publish": "25.1.7", + "electron-publish": "24.13.1", "form-data": "^4.0.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "is-ci": "^3.0.0", "isbinaryfile": "^5.0.0", "js-yaml": "^4.1.0", - "json5": "^2.2.3", "lazy-val": "^1.0.5", - "minimatch": "^10.0.0", - "resedit": "^1.7.0", + "minimatch": "^5.1.1", + "read-config-file": "6.3.2", "sanitize-filename": "^1.6.3", "semver": "^7.3.8", "tar": "^6.1.12", @@ -2679,8 +2529,8 @@ "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "25.1.8", - "electron-builder-squirrel-windows": "25.1.8" + "dmg-builder": "24.13.3", + "electron-builder-squirrel-windows": "24.13.3" } }, "node_modules/app-builder-lib/node_modules/fs-extra": { @@ -2688,6 +2538,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -2702,6 +2553,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -2709,20 +2561,12 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/app-builder-lib/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/app-builder-lib/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -2736,6 +2580,7 @@ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -2753,6 +2598,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -2761,19 +2607,15 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/aproba": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", - "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "archiver-utils": "^2.1.0", @@ -2793,6 +2635,7 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "glob": "^7.1.4", @@ -2815,6 +2658,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "core-util-is": "~1.0.0", @@ -2831,6 +2675,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { @@ -2838,25 +2683,12 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } }, - "node_modules/are-we-there-yet": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", - "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/arg": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", @@ -2866,14 +2698,14 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=0.8" @@ -2884,6 +2716,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=8" @@ -2893,13 +2726,15 @@ "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/async-exit-hook": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2914,6 +2749,7 @@ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, + "license": "ISC", "engines": { "node": ">= 4.0.0" } @@ -2965,13 +2801,11 @@ } }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "engines": { - "node": "18 || 20 || >=22" - } + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -3029,6 +2863,8 @@ "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -3054,6 +2890,8 @@ "url": "https://feross.org/support" } ], + "license": "MIT", + "peer": true, "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -3063,13 +2901,15 @@ "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/bluebird-lst": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", "dev": true, + "license": "MIT", "dependencies": { "bluebird": "^3.5.5" } @@ -3082,15 +2922,13 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, + "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -3169,29 +3007,44 @@ "node": "*" } }, + "node_modules/buffer-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/builder-util": { - "version": "25.1.7", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-25.1.7.tgz", - "integrity": "sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==", + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", + "integrity": "sha512-NhbCSIntruNDTOVI9fdXz0dihaqX2YuE1D6zZMrwiErzH4ELZHE6mdiB40wEgZNprDia+FghRFgKoAqMZRRjSA==", "dev": true, + "license": "MIT", "dependencies": { "@types/debug": "^4.1.6", "7zip-bin": "~5.2.0", - "app-builder-bin": "5.0.0-alpha.10", + "app-builder-bin": "4.0.0", "bluebird-lst": "^1.0.9", - "builder-util-runtime": "9.2.10", + "builder-util-runtime": "9.2.4", "chalk": "^4.1.2", "cross-spawn": "^7.0.3", "debug": "^4.3.4", "fs-extra": "^10.1.0", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", "is-ci": "^3.0.0", "js-yaml": "^4.1.0", "source-map-support": "^0.5.19", @@ -3200,10 +3053,11 @@ } }, "node_modules/builder-util-runtime": { - "version": "9.2.10", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.10.tgz", - "integrity": "sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.4.tgz", + "integrity": "sha512-upp+biKpN/XZMLim7aguUyW8s0FUpDvOtK6sbanMFDAMBzpHDqdhgVYm6zc9HJ6nWo7u2Lxk60i2M6Jd3aiNrA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -3217,6 +3071,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -3231,6 +3086,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -3243,6 +3099,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -3256,124 +3113,6 @@ "node": ">=8" } }, - "node_modules/cacache": { - "version": "16.1.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", - "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", - "dev": true, - "dependencies": { - "@npmcli/fs": "^2.1.0", - "@npmcli/move-file": "^2.0.0", - "chownr": "^2.0.0", - "fs-minipass": "^2.1.0", - "glob": "^8.0.1", - "infer-owner": "^1.0.4", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "mkdirp": "^1.0.4", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^9.0.0", - "tar": "^6.1.11", - "unique-filename": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/cacache/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/cacache/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/cacache/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/cacache/node_modules/minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cacache/node_modules/tar/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -3514,6 +3253,7 @@ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -3522,7 +3262,8 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ci-info": { "version": "3.9.0", @@ -3535,48 +3276,17 @@ "url": "https://github.com/sponsors/sibiraj-s" } ], + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-truncate": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "slice-ansi": "^3.0.0", @@ -3594,6 +3304,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -3603,15 +3314,6 @@ "node": ">=12" } }, - "node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -3641,15 +3343,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3666,6 +3359,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -3675,6 +3369,7 @@ "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -3684,6 +3379,7 @@ "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", @@ -3702,13 +3398,14 @@ "dev": true }, "node_modules/config-file-ts": { - "version": "0.2.8-rc1", - "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.8-rc1.tgz", - "integrity": "sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==", + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.6.tgz", + "integrity": "sha512-6boGVaglwblBgJqGyxm4+xCmEGcWgnWHSWHY5jad58awQhB6gftq0G8HbzU39YqCIYHMLAiL1yjwiZ36m/CL8w==", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^10.3.12", - "typescript": "^5.4.3" + "glob": "^10.3.10", + "typescript": "^5.3.3" } }, "node_modules/config-file-ts/node_modules/glob": { @@ -3717,6 +3414,7 @@ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", @@ -3733,12 +3431,13 @@ } }, "node_modules/config-file-ts/node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -3752,16 +3451,11 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", - "dev": true - }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3772,13 +3466,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/crc": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "buffer": "^5.1.0" @@ -3789,6 +3485,7 @@ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, + "license": "Apache-2.0", "peer": true, "bin": { "crc32": "bin/crc32.njs" @@ -3816,6 +3513,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "optional": true, "dependencies": { "base64-js": "^1.3.1", @@ -3827,6 +3525,7 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "crc-32": "^1.2.0", @@ -3931,18 +3630,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", - "dev": true, - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -3993,12 +3680,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true - }, "node_modules/detect-gpu": { "version": "5.0.70", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", @@ -4007,15 +3688,6 @@ "webgl-constants": "^1.1.1" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", @@ -4029,36 +3701,33 @@ "dev": true }, "node_modules/dir-compare": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", - "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-3.3.0.tgz", + "integrity": "sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==", "dev": true, + "license": "MIT", "dependencies": { - "minimatch": "^3.0.5", - "p-limit": "^3.1.0 " + "buffer-equal": "^1.0.0", + "minimatch": "^3.0.4" } }, - "node_modules/dir-compare/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, "node_modules/dir-compare/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/dir-compare/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4073,14 +3742,15 @@ "dev": true }, "node_modules/dmg-builder": { - "version": "25.1.8", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-25.1.8.tgz", - "integrity": "sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-24.13.3.tgz", + "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", "dev": true, + "license": "MIT", "dependencies": { - "app-builder-lib": "25.1.8", - "builder-util": "25.1.7", - "builder-util-runtime": "9.2.10", + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", "fs-extra": "^10.1.0", "iconv-lite": "^0.6.2", "js-yaml": "^4.1.0" @@ -4094,6 +3764,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -4108,6 +3779,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -4120,6 +3792,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -4129,6 +3802,7 @@ "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -4151,31 +3825,21 @@ } }, "node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">=10" } }, "node_modules/dotenv-expand": { - "version": "11.0.7", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", - "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", "dev": true, - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } + "license": "BSD-2-Clause" }, "node_modules/draco3d": { "version": "1.5.7", @@ -4199,13 +3863,15 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -4234,19 +3900,21 @@ } }, "node_modules/electron-builder": { - "version": "25.1.8", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-25.1.8.tgz", - "integrity": "sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-24.13.3.tgz", + "integrity": "sha512-yZSgVHft5dNVlo31qmJAe4BVKQfFdwpRw7sFp1iQglDRCDD6r22zfRJuZlhtB5gp9FHUxCMEoWGq10SkCnMAIg==", "dev": true, + "license": "MIT", "dependencies": { - "app-builder-lib": "25.1.8", - "builder-util": "25.1.7", - "builder-util-runtime": "9.2.10", + "app-builder-lib": "24.13.3", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", "chalk": "^4.1.2", - "dmg-builder": "25.1.8", + "dmg-builder": "24.13.3", "fs-extra": "^10.1.0", "is-ci": "^3.0.0", "lazy-val": "^1.0.5", + "read-config-file": "6.3.2", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, @@ -4259,15 +3927,16 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "25.1.8", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-25.1.8.tgz", - "integrity": "sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==", + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-24.13.3.tgz", + "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "25.1.8", + "app-builder-lib": "24.13.3", "archiver": "^5.3.1", - "builder-util": "25.1.7", + "builder-util": "24.13.1", "fs-extra": "^10.1.0" } }, @@ -4276,6 +3945,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "graceful-fs": "^4.2.0", @@ -4291,6 +3961,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "universalify": "^2.0.0" @@ -4304,6 +3975,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">= 10.0.0" @@ -4345,14 +4017,15 @@ } }, "node_modules/electron-publish": { - "version": "25.1.7", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-25.1.7.tgz", - "integrity": "sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==", + "version": "24.13.1", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-24.13.1.tgz", + "integrity": "sha512-2ZgdEqJ8e9D17Hwp5LEq5mLQPjqU3lv/IALvgp+4W8VeNhryfGhYEQC/PgDPMrnWUp+l60Ou5SJLsu+k4mhQ8A==", "dev": true, + "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", - "builder-util": "25.1.7", - "builder-util-runtime": "9.2.10", + "builder-util": "24.13.1", + "builder-util-runtime": "9.2.4", "chalk": "^4.1.2", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", @@ -4364,6 +4037,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -4378,6 +4052,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -4390,6 +4065,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -4400,18 +4076,94 @@ "integrity": "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==", "dev": true }, - "node_modules/electron-vite": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz", - "integrity": "sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==", - "dev": true, + "node_modules/electron-updater": { + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.3.tgz", + "integrity": "sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==", + "license": "MIT", "dependencies": { - "@babel/core": "^7.24.7", - "@babel/plugin-transform-arrow-functions": "^7.24.7", - "cac": "^6.7.14", - "esbuild": "^0.21.5", - "magic-string": "^0.30.10", - "picocolors": "^1.0.1" + "builder-util-runtime": "9.5.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.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/builder-util-runtime": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.5.1.tgz", + "integrity": "sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/electron-updater/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-updater/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-updater/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-vite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/electron-vite/-/electron-vite-2.3.0.tgz", + "integrity": "sha512-lsN2FymgJlp4k6MrcsphGqZQ9fKRdJKasoaiwIrAewN1tapYI/KINLdfEL7n10LuF0pPSNf/IqjzZbB5VINctg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "cac": "^6.7.14", + "esbuild": "^0.21.5", + "magic-string": "^0.30.10", + "picocolors": "^1.0.1" }, "bin": { "electron-vite": "bin/electron-vite.js" @@ -4441,17 +4193,8 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "dev": true, - "optional": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } + "license": "MIT" }, "node_modules/end-of-stream": { "version": "1.4.5", @@ -4473,7 +4216,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/es-define-property": { "version": "1.0.1", @@ -4668,12 +4412,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -4755,12 +4493,6 @@ "node": ">=0.10.0" } }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", - "dev": true - }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -4788,6 +4520,7 @@ "engines": [ "node >=0.6.0" ], + "license": "MIT", "optional": true }, "node_modules/fast-deep-equal": { @@ -4871,15 +4604,13 @@ } }, "node_modules/filelist": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.5.tgz", - "integrity": "sha512-ct/ckWBV/9Dg3MlvCXsLcSUyoWwv9mCKqlhLNB2DAuXR/NZolSXlQqP5dyy6guWlPXBhodZyZ5lGPQcbQDxrEQ==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "minimatch": "^10.2.1" - }, - "engines": { - "node": "20 || >=22" + "minimatch": "^5.0.1" } }, "node_modules/fill-range": { @@ -4953,6 +4684,7 @@ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" @@ -4964,18 +4696,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/foreground-child/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -5009,6 +4729,7 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fs-extra": { @@ -5029,6 +4750,7 @@ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, + "license": "ISC", "dependencies": { "minipass": "^3.0.0" }, @@ -5036,11 +4758,32 @@ "node": ">= 8" } }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-minipass/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -5064,26 +4807,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5098,6 +4821,7 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -5157,6 +4881,7 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5184,27 +4909,23 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5360,12 +5081,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", - "dev": true - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -5387,6 +5102,7 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, + "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, @@ -5399,6 +5115,7 @@ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, + "license": "ISC", "dependencies": { "yallist": "^4.0.0" }, @@ -5410,7 +5127,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/http-cache-semantics": { "version": "4.2.0", @@ -5418,16 +5136,18 @@ "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==" }, "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/http2-wrapper": { @@ -5443,25 +5163,17 @@ } }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, + "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", - "dev": true, - "dependencies": { - "ms": "^2.0.0" + "node": ">= 6" } }, "node_modules/iconv-corefoundation": { @@ -5469,6 +5181,7 @@ "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5486,6 +5199,7 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, + "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -5551,27 +5265,13 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -5581,16 +5281,8 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, - "engines": { - "node": ">= 12" - } + "license": "ISC" }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -5609,6 +5301,7 @@ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, + "license": "MIT", "dependencies": { "ci-info": "^3.2.0" }, @@ -5645,6 +5338,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5661,21 +5355,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-lambda": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", - "dev": true - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5690,23 +5369,12 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/isbinaryfile": { @@ -5714,6 +5382,7 @@ "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 18.0.0" }, @@ -5750,6 +5419,7 @@ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -5765,6 +5435,7 @@ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, + "license": "Apache-2.0", "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", @@ -5795,7 +5466,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -5870,13 +5540,14 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", - "dev": true + "license": "MIT" }, "node_modules/lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "readable-stream": "^2.0.5" @@ -5890,6 +5561,7 @@ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "core-util-is": "~1.0.0", @@ -5906,6 +5578,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { @@ -5913,6 +5586,7 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "~5.1.0" @@ -5976,13 +5650,15 @@ "version": "4.17.23", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/lodash.difference": { @@ -5990,20 +5666,36 @@ "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, + "license": "MIT", "peer": true }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, + "license": "MIT", "peer": true }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/lodash.merge": { @@ -6017,24 +5709,9 @@ "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, + "license": "MIT", "peer": true }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6081,81 +5758,6 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/make-fetch-happen": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", - "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", - "dev": true, - "dependencies": { - "agentkeepalive": "^4.2.1", - "cacache": "^16.1.0", - "http-cache-semantics": "^4.1.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.0", - "is-lambda": "^1.0.1", - "lru-cache": "^7.7.1", - "minipass": "^3.1.6", - "minipass-collect": "^1.0.2", - "minipass-fetch": "^2.0.3", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.4", - "negotiator": "^0.6.3", - "promise-retry": "^2.0.1", - "socks-proxy-agent": "^7.0.0", - "ssri": "^9.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/make-fetch-happen/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -6216,6 +5818,7 @@ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, + "license": "MIT", "bin": { "mime": "cli.js" }, @@ -6242,15 +5845,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mimic-response": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", @@ -6260,18 +5854,16 @@ } }, "node_modules/minimatch": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", - "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, "node_modules/minimist": { @@ -6279,117 +5871,61 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, + "license": "ISC", "engines": { "node": ">=8" } }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-fetch": { + "node_modules/minizlib": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", - "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", - "dev": true, - "dependencies": { - "minipass": "^3.1.6", - "minipass-sized": "^1.0.3", - "minizlib": "^2.1.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - }, - "optionalDependencies": { - "encoding": "^0.1.13" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0" + "minipass": "^3.0.0", + "yallist": "^4.0.0" }, "engines": { "node": ">= 8" } }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, + "license": "ISC", "dependencies": { - "minipass": "^3.0.0", "yallist": "^4.0.0" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -6437,136 +5973,13 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-abi": { - "version": "3.87.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", - "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", - "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", - "dev": true, - "optional": true - }, - "node_modules/node-api-version": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", - "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", - "dev": true, - "dependencies": { - "semver": "^7.3.5" - } - }, - "node_modules/node-api-version/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp": { - "version": "9.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.4.1.tgz", - "integrity": "sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==", - "dev": true, - "dependencies": { - "env-paths": "^2.2.0", - "exponential-backoff": "^3.1.1", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^10.0.3", - "nopt": "^6.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": "^12.13 || ^14.13 || >=16" - } - }, - "node_modules/node-gyp/node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-gyp/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true }, "node_modules/node-releases": { "version": "2.0.27", @@ -6574,21 +5987,6 @@ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true }, - "node_modules/nopt": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", - "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", - "dev": true, - "dependencies": { - "abbrev": "^1.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6609,22 +6007,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "deprecated": "This package is no longer supported.", - "dev": true, - "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -6659,21 +6041,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -6691,29 +6058,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -6752,26 +6096,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/parent-module": { "version": "1.0.1", @@ -6799,6 +6129,7 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6822,6 +6153,7 @@ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -6837,30 +6169,8 @@ "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true - }, - "node_modules/path-scurry/node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/pe-library": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", - "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", "dev": true, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } + "license": "ISC" }, "node_modules/pend": { "version": "1.2.0", @@ -6908,6 +6218,7 @@ "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", "dev": true, + "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", @@ -7092,6 +6403,7 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/progress": { @@ -7102,17 +6414,12 @@ "node": ">=0.4.0" } }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", - "dev": true - }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "dev": true, + "license": "MIT", "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -7279,18 +6586,6 @@ } } }, - "node_modules/read-binary-file-arch": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", - "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", - "dev": true, - "dependencies": { - "debug": "^4.3.4" - }, - "bin": { - "read-binary-file-arch": "cli.js" - } - }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -7300,11 +6595,31 @@ "pify": "^2.3.0" } }, + "node_modules/read-config-file": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.3.2.tgz", + "integrity": "sha512-M80lpCjnE6Wt6zb98DoW8WHR09nzMSpu8XHtPkiTHrJ5Az9CybfeQhTJ8D7saeBHpGhLPIVyA8lcL6ZmdKwY6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "config-file-ts": "^0.2.4", + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -7319,41 +6634,12 @@ "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "minimatch": "^5.1.0" } }, - "node_modules/readdir-glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "peer": true - }, - "node_modules/readdir-glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "peer": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/readdir-glob/node_modules/minimatch": { - "version": "5.1.7", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.7.tgz", - "integrity": "sha512-FjiwU9HaHW6YB3H4a1sFudnv93lvydNjz2lmyUXR6IwKhGI+bgL3SOZrBGn6kvvX2pJvhEkGSGjyTHN47O4rqA==", - "dev": true, - "peer": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7371,6 +6657,7 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7383,23 +6670,6 @@ "node": ">=0.10.0" } }, - "node_modules/resedit": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", - "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", - "dev": true, - "dependencies": { - "pe-library": "^0.4.1" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/jet2jet" - } - }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -7445,24 +6715,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -7477,22 +6735,6 @@ "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/roarr": { "version": "2.15.4", "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", @@ -7595,28 +6837,32 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT", + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/sanitize-filename": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", - "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", "dev": true, + "license": "WTFPL OR ISC", "dependencies": { "truncate-utf8-bytes": "^1.0.0" } }, "node_modules/sax": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", - "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", - "dev": true, + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", "engines": { "node": ">=11.0.0" } @@ -7658,12 +6904,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7684,10 +6924,17 @@ } }, "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, "node_modules/simple-update-notifier": { "version": "2.0.0", @@ -7718,6 +6965,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "ansi-styles": "^4.0.0", @@ -7733,56 +6981,19 @@ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" } }, - "node_modules/socks": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", - "dev": true, - "dependencies": { - "ip-address": "^10.0.1", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", - "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.3", - "socks": "^2.6.2" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/socks-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -7801,6 +7012,7 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -7812,23 +7024,12 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "optional": true }, - "node_modules/ssri": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", - "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -7861,6 +7062,8 @@ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, + "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -7870,6 +7073,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7885,6 +7089,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7899,6 +7104,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -7912,6 +7118,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -8062,6 +7269,7 @@ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "bl": "^4.0.3", @@ -8114,6 +7322,7 @@ "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", "dev": true, + "license": "MIT", "dependencies": { "async-exit-hook": "^2.0.1", "fs-extra": "^10.0.0" @@ -8124,6 +7333,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -8138,6 +7348,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -8150,6 +7361,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -8210,6 +7422,12 @@ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==" }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -8260,6 +7478,7 @@ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", "dev": true, + "license": "MIT", "engines": { "node": ">=14.14" } @@ -8269,6 +7488,7 @@ "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", "dev": true, + "license": "MIT", "dependencies": { "tmp": "^0.2.0" } @@ -8317,6 +7537,7 @@ "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", "dev": true, + "license": "WTFPL", "dependencies": { "utf8-byte-length": "^1.0.1" } @@ -8404,30 +7625,6 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, - "node_modules/unique-filename": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", - "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", - "dev": true, - "dependencies": { - "unique-slug": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/unique-slug": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", - "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, "node_modules/universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -8487,7 +7684,8 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", - "dev": true + "dev": true, + "license": "(WTFPL OR MIT)" }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -8508,6 +7706,7 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "assert-plus": "^1.0.0", @@ -8577,15 +7776,6 @@ } } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "dev": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/webgl-constants": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", @@ -8610,15 +7800,6 @@ "node": ">= 8" } }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -8633,6 +7814,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8651,6 +7833,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -8673,6 +7856,7 @@ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.0" } @@ -8682,6 +7866,7 @@ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -8697,6 +7882,7 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -8715,6 +7901,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -8745,6 +7932,7 @@ "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "archiver-utils": "^3.0.4", @@ -8760,6 +7948,7 @@ "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "glob": "^7.2.3", diff --git a/package.json b/package.json index 3dfd22e..484efc6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "modly", - "version": "0.1.3", + "version": "0.1.4", "description": "Local AI-powered 3D mesh generation from images", "main": "./out/main/index.js", "author": "Modly", @@ -18,6 +18,7 @@ "@react-three/drei": "^9.120.0", "@react-three/fiber": "^8.17.10", "axios": "^1.7.9", + "electron-updater": "^6.8.3", "react": "^18.3.1", "react-dom": "^18.3.1", "tar": "^7.5.9", @@ -34,7 +35,7 @@ "autoprefixer": "^10.4.20", "cross-env": "^10.1.0", "electron": "^33.3.0", - "electron-builder": "^25.1.8", + "electron-builder": "^24.13.3", "electron-vite": "^2.3.0", "eslint": "^9.17.0", "postcss": "^8.4.49", @@ -62,6 +63,11 @@ ] } ], + "publish": { + "provider": "github", + "owner": "lightningpixel", + "repo": "modly" + }, "nsis": { "oneClick": false, "allowToChangeInstallationDirectory": true diff --git a/src/App.tsx b/src/App.tsx index 13e0ca2..e115217 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,26 +5,25 @@ import MainLayout from '@shared/components/layout/MainLayout' import { UpdateModal } from '@shared/components/ui/UpdateModal' import { ErrorModal } from '@shared/components/ui/ErrorModal' -function compareSemver(a: string, b: string): number { - const pa = a.replace(/^v/, '').split('.').map(Number) - const pb = b.replace(/^v/, '').split('.').map(Number) - for (let i = 0; i < 3; i++) { - if ((pa[i] ?? 0) > (pb[i] ?? 0)) return 1 - if ((pa[i] ?? 0) < (pb[i] ?? 0)) return -1 - } - return 0 -} - export default function App(): JSX.Element { - const { checkSetup, setupStatus, initApp, backendStatus, showError } = useAppStore() + const { checkSetup, setupStatus, initApp, backendStatus, showError, setPatchUpdateReady } = useAppStore() const [updateVersion, setUpdateVersion] = useState(null) const [currentVersion, setCurrentVersion] = useState('') useEffect(() => { checkSetup() window.electron.app.onError((message) => showError(message)) - -return () => { window.electron.app.offError() } + window.electron.updater.onPatchReady(() => { + setPatchUpdateReady(true) + }) + window.electron.updater.onMajorMinorAvailable(({ version }) => { + setUpdateVersion(`v${version}`) + }) + return () => { + window.electron.app.offError() + window.electron.updater.offPatchReady() + window.electron.updater.offMajorMinorAvailable() + } }, []) useEffect(() => { @@ -35,13 +34,7 @@ return () => { window.electron.app.offError() } if (backendStatus !== 'ready') return window.electron.app.info().then(({ version }) => { setCurrentVersion(version) - fetch('https://api.github.com/repos/lightningpixel/modly/releases/latest') - .then((r) => r.json()) - .then((data) => { - const latest = data?.tag_name as string | undefined - if (latest && compareSemver(latest, version) > 0) setUpdateVersion(latest) - }) - .catch(() => {}) + window.electron.updater.check() }) }, [backendStatus]) diff --git a/src/shared/components/layout/TopBar.tsx b/src/shared/components/layout/TopBar.tsx index c8e66ed..411d8d1 100644 --- a/src/shared/components/layout/TopBar.tsx +++ b/src/shared/components/layout/TopBar.tsx @@ -1,4 +1,7 @@ +import { useAppStore } from '@shared/stores/appStore' + export default function TopBar(): JSX.Element { + const { patchUpdateReady } = useAppStore() const handleMinimize = () => window.electron.window.minimize() const handleMaximize = () => window.electron.window.maximize() @@ -29,6 +32,19 @@ export default function TopBar(): JSX.Element { {/* Spacer */}
+ {/* Patch update badge */} + {patchUpdateReady && ( +
+ Update ready + +
+ )} + {/* Window controls */}
) diff --git a/src/shared/types/electron.d.ts b/src/shared/types/electron.d.ts index 3d9a4c0..95e62d0 100644 --- a/src/shared/types/electron.d.ts +++ b/src/shared/types/electron.d.ts @@ -42,6 +42,7 @@ declare global { isDownloaded: (modelId: string) => Promise download: (repoId: string, modelId: string) => Promise<{ success: boolean; error?: string }> delete: (modelId: string) => Promise<{ success: boolean; error?: string }> + unloadAll: () => Promise<{ success: boolean; error?: string }> onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => void offProgress: () => void } From 75dad36c5fec6900ddc5c1c102335c4cfec0150f Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sat, 21 Mar 2026 23:02:51 +0100 Subject: [PATCH 13/20] feat(generate): add stop generation button with cooperative cancellation --- api/routers/generation.py | 36 +++++++++++++++++++++++++++-- api/schemas/generation.py | 2 +- api/services/generators/base.py | 11 +++++++++ src/areas/generate/GeneratePage.tsx | 29 +++++++++++++++-------- src/shared/hooks/useApi.ts | 6 ++++- src/shared/hooks/useGeneration.ts | 33 ++++++++++++++++++++++---- 6 files changed, 98 insertions(+), 19 deletions(-) diff --git a/api/routers/generation.py b/api/routers/generation.py index 8bdde97..ea9dead 100644 --- a/api/routers/generation.py +++ b/api/routers/generation.py @@ -4,7 +4,7 @@ import uuid from typing import Dict from fastapi import APIRouter, File, Form, UploadFile, HTTPException, BackgroundTasks -from services.generators.base import smooth_progress +from services.generators.base import smooth_progress, GenerationCancelled import re as _re from services.generator_registry import generator_registry, WORKSPACE_DIR @@ -13,6 +13,8 @@ router = APIRouter(tags=["generation"]) _jobs: Dict[str, JobStatus] = {} +_cancelled: set = set() +_cancel_events: Dict[str, threading.Event] = {} @router.post("/from-image") @@ -71,6 +73,7 @@ async def generate_from_image( job = JobStatus(job_id=job_id, status="pending", progress=0) _jobs[job_id] = job + _cancel_events[job_id] = threading.Event() background_tasks.add_task(_run_generation, job_id, image_bytes, params, collection) @@ -86,6 +89,19 @@ async def job_status(job_id: str): return job +@router.post("/cancel/{job_id}") +async def cancel_job(job_id: str): + job = _jobs.get(job_id) + if not job: + raise HTTPException(404, f"Job {job_id} not found") + _cancelled.add(job_id) + if job_id in _cancel_events: + _cancel_events[job_id].set() + if job.status in ("pending", "running"): + job.status = "cancelled" + return {"cancelled": True} + + async def _run_generation(job_id: str, image_bytes: bytes, params: dict, collection: str = "Default") -> None: job = _jobs[job_id] job.status = "running" @@ -118,20 +134,36 @@ def progress_cb(pct: int, step: str = "") -> None: else: gen = await loop.run_in_executor(None, generator_registry.get_active) + if job_id in _cancelled: + return + # Direct output to the collection subfolder coll_dir = WORKSPACE_DIR / collection coll_dir.mkdir(parents=True, exist_ok=True) gen.outputs_dir = coll_dir + cancel_event = _cancel_events.get(job_id) + import inspect + supports_cancel = "cancel_event" in inspect.signature(gen.generate).parameters output_path = await loop.run_in_executor( None, - lambda: gen.generate(image_bytes, params, progress_cb), + lambda: gen.generate(image_bytes, params, progress_cb, cancel_event) + if supports_cancel + else gen.generate(image_bytes, params, progress_cb), ) + + if job_id in _cancelled: + return + job.status = "done" job.progress = 100 job.output_url = f"/workspace/{collection}/{output_path.name}" + except GenerationCancelled: + job.status = "cancelled" except Exception as exc: + if job_id in _cancelled: + return tb = traceback.format_exc() print(f"[Generation ERROR] {exc}\n{tb}") job.status = "error" diff --git a/api/schemas/generation.py b/api/schemas/generation.py index 960318c..7ed6ca6 100644 --- a/api/schemas/generation.py +++ b/api/schemas/generation.py @@ -4,7 +4,7 @@ class JobStatus(BaseModel): job_id: str - status: Literal["pending", "running", "done", "error"] + status: Literal["pending", "running", "done", "error", "cancelled"] progress: int = 0 # 0–100 step: Optional[str] = None # Human-readable current step output_url: Optional[str] = None diff --git a/api/services/generators/base.py b/api/services/generators/base.py index fdcb32a..0180f93 100644 --- a/api/services/generators/base.py +++ b/api/services/generators/base.py @@ -7,6 +7,10 @@ from typing import Callable, Optional +class GenerationCancelled(Exception): + """Raised by generators when a cancel_event is set mid-generation.""" + + def smooth_progress( progress_cb: Callable[[int, str], None], start: int, @@ -87,14 +91,21 @@ def generate( image_bytes: bytes, params: dict, progress_cb: Optional[Callable[[int, str], None]] = None, + cancel_event: Optional[threading.Event] = None, ) -> Path: """ Starts 3D generation from an image. Returns the path to the generated .glb file. progress_cb(percent: int, step_label: str) + cancel_event: set this to interrupt generation between steps. """ ... + def _check_cancelled(self, cancel_event: Optional[threading.Event]) -> None: + """Raises GenerationCancelled if cancel_event is set.""" + if cancel_event and cancel_event.is_set(): + raise GenerationCancelled() + # ------------------------------------------------------------------ # # Parameter schema (for the UI) # ------------------------------------------------------------------ # diff --git a/src/areas/generate/GeneratePage.tsx b/src/areas/generate/GeneratePage.tsx index a99db6f..9bb1747 100644 --- a/src/areas/generate/GeneratePage.tsx +++ b/src/areas/generate/GeneratePage.tsx @@ -10,7 +10,7 @@ import Viewer3D from './components/Viewer3D' export default function GeneratePage(): JSX.Element { const selectedImagePath = useAppStore((s) => s.selectedImagePath) const modelId = useAppStore((s) => s.generationOptions.modelId) - const { currentJob, startGeneration } = useGeneration() + const { currentJob, startGeneration, cancelGeneration } = useGeneration() const isGenerating = currentJob?.status === 'uploading' || currentJob?.status === 'generating' const [unloadStatus, setUnloadStatus] = useState<'idle' | 'done'>('idle') @@ -33,16 +33,25 @@ export default function GeneratePage(): JSX.Element {
- {/* Sticky bottom: Generate button */} + {/* Sticky bottom: Generate / Stop button */}
- + {isGenerating ? ( + + ) : ( + + )}
diff --git a/src/shared/hooks/useApi.ts b/src/shared/hooks/useApi.ts index 9cd335c..a53da2c 100644 --- a/src/shared/hooks/useApi.ts +++ b/src/shared/hooks/useApi.ts @@ -95,5 +95,9 @@ export function useApi() { return { url: data.url, faceCount: data.face_count } } - return { generateFromImage, pollJobStatus, getModelStatus, downloadModel, optimizeMesh } + async function cancelJob(jobId: string): Promise { + await client.post(`/generate/cancel/${jobId}`).catch(() => {}) + } + + return { generateFromImage, pollJobStatus, cancelJob, getModelStatus, downloadModel, optimizeMesh } } diff --git a/src/shared/hooks/useGeneration.ts b/src/shared/hooks/useGeneration.ts index e4c5970..5a80b22 100644 --- a/src/shared/hooks/useGeneration.ts +++ b/src/shared/hooks/useGeneration.ts @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useRef } from 'react' import { useAppStore } from '@shared/stores/appStore' import { useCollectionsStore } from '@shared/stores/collectionsStore' import { useApi } from './useApi' @@ -7,10 +7,12 @@ export function useGeneration() { const { currentJob, setCurrentJob, updateCurrentJob, generationOptions, selectedImageData } = useAppStore() const addToWorkspace = useCollectionsStore((s) => s.addToWorkspace) const activeCollectionId = useCollectionsStore((s) => s.activeCollectionId) - const { generateFromImage, pollJobStatus } = useApi() + const { generateFromImage, pollJobStatus, cancelJob } = useApi() + const cancelledRef = useRef(false) const startGeneration = useCallback( async (imagePath: string) => { + cancelledRef.current = false const job = { id: crypto.randomUUID(), imageFile: imagePath, @@ -25,25 +27,42 @@ export function useGeneration() { try { const { jobId } = await generateFromImage(imagePath, generationOptions, activeCollectionId, selectedImageData ?? undefined) + if (cancelledRef.current) { + await cancelJob(jobId) + return + } + updateCurrentJob({ status: 'generating', progress: 0 }) - // Poll until done await pollUntilDone(jobId) } catch (err) { + if (cancelledRef.current) return updateCurrentJob({ status: 'error', error: err instanceof Error ? err.message : String(err) }) } }, - [generateFromImage, pollJobStatus, setCurrentJob, updateCurrentJob, addToWorkspace, activeCollectionId] + [generateFromImage, pollJobStatus, cancelJob, setCurrentJob, updateCurrentJob, addToWorkspace, activeCollectionId] ) const pollUntilDone = async (jobId: string) => { while (true) { await new Promise((r) => setTimeout(r, 1000)) + + if (cancelledRef.current) { + await cancelJob(jobId) + setCurrentJob(null) + break + } + const result = await pollJobStatus(jobId) + if (result.status === 'cancelled') { + setCurrentJob(null) + break + } + if (result.status === 'done') { updateCurrentJob({ status: 'done', progress: 100, outputUrl: result.outputUrl, originalOutputUrl: result.outputUrl }) const finalJob = useAppStore.getState().currentJob @@ -63,7 +82,11 @@ export function useGeneration() { } } + const cancelGeneration = useCallback(() => { + cancelledRef.current = true + }, []) + const reset = useCallback(() => setCurrentJob(null), [setCurrentJob]) - return { currentJob, startGeneration, reset } + return { currentJob, startGeneration, cancelGeneration, reset } } From 7605047278f8814b45ef4ad290de2e4dde8d5f7e Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 10:05:32 +0100 Subject: [PATCH 14/20] feat(models): pass hf_skip_prefixes through download pipeline and fix stop button abort --- api/routers/model.py | 21 ++++++++++++++++----- electron/main/ipc-handlers.ts | 10 +++++----- electron/main/model-downloader.ts | 12 ++++++++---- electron/preload/index.ts | 2 +- src/areas/models/ModelsPage.tsx | 25 ++++++++++++++++++------- src/shared/hooks/useApi.ts | 7 +++++++ src/shared/hooks/useGeneration.ts | 11 +++++++++-- src/shared/stores/extensionsStore.ts | 9 +++++---- src/shared/types/electron.d.ts | 2 +- 9 files changed, 70 insertions(+), 29 deletions(-) diff --git a/api/routers/model.py b/api/routers/model.py index 9326916..2e3bee0 100644 --- a/api/routers/model.py +++ b/api/routers/model.py @@ -58,19 +58,30 @@ async def unload_model(model_id: str): @router.get("/hf-download") -async def hf_download(repo_id: str, model_id: str): +async def hf_download(repo_id: str, model_id: str, skip_prefixes: Optional[str] = None): """ Streams a HuggingFace Hub model download via SSE. Downloads into MODELS_DIR / model_id applying the filtering declared in the extension manifest (hf_skip_prefixes). + skip_prefixes: JSON-encoded list of path prefixes to exclude (passed from Electron). + Falls back to registry manifest if not provided. + SSE format: data: {"percent": 0-100, "file": "...", "status": "..."} """ + import json as _json dest_dir = str(MODELS_DIR / model_id) - try: - skip_list = generator_registry.get_manifest(model_id).get("hf_skip_prefixes", []) - except KeyError: - skip_list = [] + # Prefer skip_prefixes passed directly from the client (authoritative, no registry dep) + if skip_prefixes: + try: + skip_list = _json.loads(skip_prefixes) + except Exception: + skip_list = [] + else: + try: + skip_list = generator_registry.get_manifest(model_id).get("hf_skip_prefixes", []) + except KeyError: + skip_list = [] async def stream(): loop = asyncio.get_running_loop() diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index e17705d..68236e6 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -175,11 +175,11 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe return isModelDownloaded(modelsDir, modelId) }) - ipcMain.handle('model:download', async (event, { repoId, modelId }: { repoId: string; modelId: string }) => { + ipcMain.handle('model:download', async (event, { repoId, modelId, skipPrefixes }: { repoId: string; modelId: string; skipPrefixes?: string[] }) => { try { await downloadModelFromHF(repoId, modelId, (progress) => { event.sender.send('model:downloadProgress', { modelId, ...progress }) - }) + }, skipPrefixes) return { success: true } } catch (err) { return { success: false, error: String(err) } @@ -381,15 +381,15 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe description?: string; author?: string | { name?: string } hf_repo?: string; source?: string; generator_class?: string model?: { repoId?: string; modelId?: string } - models?: { id?: string; name?: string; hf_repo?: string; description?: string }[] + models?: { id?: string; name?: string; hf_repo?: string; description?: string; hf_skip_prefixes?: string[] }[] } function parseExtensionManifest(parsed: ParsedManifest, fallbackId: string, trustedRepos: Set) { - let models: { id: string; name: string; repoId: string; description?: string }[] = [] + let models: { id: string; name: string; repoId: string; description?: string; hfSkipPrefixes?: string[] }[] = [] if (parsed.models?.length) { models = parsed.models .filter(v => v.hf_repo && v.id) - .map(v => ({ id: v.id!, name: v.name ?? v.id!, repoId: v.hf_repo!, description: v.description })) + .map(v => ({ id: v.id!, name: v.name ?? v.id!, repoId: v.hf_repo!, description: v.description, hfSkipPrefixes: v.hf_skip_prefixes })) } else { const repoId = parsed.model?.repoId ?? parsed.hf_repo const modelId = parsed.model?.modelId ?? parsed.id ?? fallbackId diff --git a/electron/main/model-downloader.ts b/electron/main/model-downloader.ts index e3591a0..544adfd 100644 --- a/electron/main/model-downloader.ts +++ b/electron/main/model-downloader.ts @@ -106,12 +106,16 @@ export function listDownloadedModels(modelsDir: string): { id: string; name: str * Reports progress (0–100) via the onProgress callback. */ export async function downloadModelFromHF( - repoId: string, - modelId: string, - onProgress: ProgressCallback + repoId: string, + modelId: string, + onProgress: ProgressCallback, + skipPrefixes?: string[], ): Promise { const { net } = require('electron') - const url = `${PYTHON_API_URL}/model/hf-download?repo_id=${encodeURIComponent(repoId)}&model_id=${encodeURIComponent(modelId)}` + let url = `${PYTHON_API_URL}/model/hf-download?repo_id=${encodeURIComponent(repoId)}&model_id=${encodeURIComponent(modelId)}` + if (skipPrefixes && skipPrefixes.length > 0) { + url += `&skip_prefixes=${encodeURIComponent(JSON.stringify(skipPrefixes))}` + } const res = await net.fetch(url) if (!res.ok) throw new Error(`HuggingFace download failed: HTTP ${res.status}`) diff --git a/electron/preload/index.ts b/electron/preload/index.ts index bc63204..794f8d4 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -68,7 +68,7 @@ contextBridge.exposeInMainWorld('electron', { export: (args: { outputUrl: string; format: string }) => ipcRenderer.invoke('model:export', args), listDownloaded: () => ipcRenderer.invoke('model:listDownloaded'), isDownloaded: (modelId: string) => ipcRenderer.invoke('model:isDownloaded', modelId), - download: (repoId: string, modelId: string) => ipcRenderer.invoke('model:download', { repoId, modelId }), + download: (repoId: string, modelId: string, skipPrefixes?: string[]) => ipcRenderer.invoke('model:download', { repoId, modelId, skipPrefixes }), delete: (modelId: string) => ipcRenderer.invoke('model:delete', modelId), unloadAll: () => ipcRenderer.invoke('model:unloadAll'), onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => { diff --git a/src/areas/models/ModelsPage.tsx b/src/areas/models/ModelsPage.tsx index 3ee66f5..701f641 100644 --- a/src/areas/models/ModelsPage.tsx +++ b/src/areas/models/ModelsPage.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { useAppStore } from '@shared/stores/appStore' import { useNavStore } from '@shared/stores/navStore' import { useExtensionsStore } from '@shared/stores/extensionsStore' +import { useApi } from '@shared/hooks/useApi' import { ConfirmModal } from '@shared/components/ui' import { LocalModel } from './models' import { formatModelName } from './utils' @@ -26,12 +27,15 @@ export default function ModelsPage(): JSX.Element { const reloadExtensions = useExtensionsStore((s) => s.reload) const clearInstall = useExtensionsStore((s) => s.clearInstallState) + const { getAllModelsStatus } = useApi() + // HF models state - const [models, setModels] = useState([]) - const [downloading, setDownloading] = useState>({}) - const [deleteTarget, setDeleteTarget] = useState(null) - const [deleteError, setDeleteError] = useState(null) - const [uninstallTarget, setUninstallTarget] = useState(null) + const [models, setModels] = useState([]) + const [installedVariantIds, setInstalledVariantIds] = useState([]) + const [downloading, setDownloading] = useState>({}) + const [deleteTarget, setDeleteTarget] = useState(null) + const [deleteError, setDeleteError] = useState(null) + const [uninstallTarget, setUninstallTarget] = useState(null) // GitHub extension install form const [showGHForm, setShowGHForm] = useState(false) @@ -43,6 +47,13 @@ export default function ModelsPage(): JSX.Element { async function refresh() { const list = await window.electron.model.listDownloaded() setModels(list) + try { + const statuses = await getAllModelsStatus() + setInstalledVariantIds(statuses.filter((s) => s.downloaded).map((s) => s.id)) + } catch { + // fallback: derive from directory list + setInstalledVariantIds(list.map((m) => m.id)) + } } useEffect(() => { @@ -261,7 +272,7 @@ export default function ModelsPage(): JSX.Element { m.id)} + installedIds={installedVariantIds} downloading={downloading} disabled={isBusy} loadError={ @@ -270,7 +281,7 @@ export default function ModelsPage(): JSX.Element { } onInstall={(variant: ExtensionVariant) => { setDownloading((prev) => ({ ...prev, [variant.id]: { percent: 0 } })) - window.electron.model.download(variant.repoId, variant.id).then((result) => { + window.electron.model.download(variant.repoId, variant.id, variant.hfSkipPrefixes).then((result) => { if (!result.success) { setDownloading((prev) => { const n = { ...prev }; delete n[variant.id]; return n }) } diff --git a/src/shared/hooks/useApi.ts b/src/shared/hooks/useApi.ts index a53da2c..4d49311 100644 --- a/src/shared/hooks/useApi.ts +++ b/src/shared/hooks/useApi.ts @@ -11,6 +11,7 @@ export function useApi() { options: GenerationOptions, collection: string = 'Default', imageData?: string, + signal?: AbortSignal, ): Promise<{ jobId: string }> { // Use provided base64 (drag & drop) or read from disk via IPC const base64 = imageData ?? await window.electron.fs.readFileBase64(imagePath) @@ -32,6 +33,7 @@ export function useApi() { formData.append('num_inference_steps', String(options.numInferenceSteps)) const { data } = await client.post<{ job_id: string }>('/generate/from-image', formData, { headers: { 'Content-Type': 'multipart/form-data' }, + signal, }) return { jobId: data.job_id } @@ -58,6 +60,11 @@ export function useApi() { return data } + async function getAllModelsStatus(): Promise<{ id: string; downloaded: boolean }[]> { + const { data } = await client.get('/model/all') + return data + } + async function downloadModel( onProgress?: (pct: number) => void ): Promise { diff --git a/src/shared/hooks/useGeneration.ts b/src/shared/hooks/useGeneration.ts index 5a80b22..7f6c383 100644 --- a/src/shared/hooks/useGeneration.ts +++ b/src/shared/hooks/useGeneration.ts @@ -9,10 +9,12 @@ export function useGeneration() { const activeCollectionId = useCollectionsStore((s) => s.activeCollectionId) const { generateFromImage, pollJobStatus, cancelJob } = useApi() const cancelledRef = useRef(false) + const abortControllerRef = useRef(null) const startGeneration = useCallback( async (imagePath: string) => { cancelledRef.current = false + abortControllerRef.current = new AbortController() const job = { id: crypto.randomUUID(), imageFile: imagePath, @@ -25,10 +27,11 @@ export function useGeneration() { setCurrentJob(job) try { - const { jobId } = await generateFromImage(imagePath, generationOptions, activeCollectionId, selectedImageData ?? undefined) + const { jobId } = await generateFromImage(imagePath, generationOptions, activeCollectionId, selectedImageData ?? undefined, abortControllerRef.current.signal) if (cancelledRef.current) { await cancelJob(jobId) + setCurrentJob(null) return } @@ -36,7 +39,10 @@ export function useGeneration() { await pollUntilDone(jobId) } catch (err) { - if (cancelledRef.current) return + if (cancelledRef.current) { + setCurrentJob(null) + return + } updateCurrentJob({ status: 'error', error: err instanceof Error ? err.message : String(err) @@ -84,6 +90,7 @@ export function useGeneration() { const cancelGeneration = useCallback(() => { cancelledRef.current = true + abortControllerRef.current?.abort() }, []) const reset = useCallback(() => setCurrentJob(null), [setCurrentJob]) diff --git a/src/shared/stores/extensionsStore.ts b/src/shared/stores/extensionsStore.ts index cf472d3..3c99eb9 100644 --- a/src/shared/stores/extensionsStore.ts +++ b/src/shared/stores/extensionsStore.ts @@ -3,10 +3,11 @@ import { create } from 'zustand' // ─── Types ──────────────────────────────────────────────────────────────────── export interface ExtensionVariant { - id: string - name: string - repoId: string - description?: string + id: string + name: string + repoId: string + description?: string + hfSkipPrefixes?: string[] } export interface Extension { diff --git a/src/shared/types/electron.d.ts b/src/shared/types/electron.d.ts index 95e62d0..4f57891 100644 --- a/src/shared/types/electron.d.ts +++ b/src/shared/types/electron.d.ts @@ -40,7 +40,7 @@ declare global { export: (args: { outputUrl: string; format: string }) => Promise<{ success: boolean; error?: string }> listDownloaded: () => Promise<{ id: string; name: string; size_gb: number }[]> isDownloaded: (modelId: string) => Promise - download: (repoId: string, modelId: string) => Promise<{ success: boolean; error?: string }> + download: (repoId: string, modelId: string, skipPrefixes?: string[]) => Promise<{ success: boolean; error?: string }> delete: (modelId: string) => Promise<{ success: boolean; error?: string }> unloadAll: () => Promise<{ success: boolean; error?: string }> onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => void From 04f3014ecb1f257d0a0c346daaa99c10382f628d Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 14:53:00 +0100 Subject: [PATCH 15/20] fix(generate): black screen when viewing a deleted workspace model --- src/areas/generate/components/Viewer3D.tsx | 190 +++++++++++------- .../generate/components/WorkspacePanel.tsx | 5 +- src/areas/workspace/WorkspacePage.tsx | 5 +- 3 files changed, 129 insertions(+), 71 deletions(-) diff --git a/src/areas/generate/components/Viewer3D.tsx b/src/areas/generate/components/Viewer3D.tsx index 5122c86..2054903 100644 --- a/src/areas/generate/components/Viewer3D.tsx +++ b/src/areas/generate/components/Viewer3D.tsx @@ -1,4 +1,5 @@ -import { Suspense, useEffect, useRef, useState } from 'react' +import { Component, Suspense, useEffect, useRef, useState } from 'react' +import type { ReactNode, ErrorInfo } from 'react' import { Canvas, useThree } from '@react-three/fiber' import { OrbitControls, useGLTF } from '@react-three/drei' import * as THREE from 'three' @@ -59,6 +60,55 @@ function CanvasCapture({ return null } +// --------------------------------------------------------------------------- +// ModelErrorBoundary — catches useGLTF load failures (e.g. 404) +// --------------------------------------------------------------------------- + +interface ErrorBoundaryProps { + children: ReactNode + fallback: ReactNode + resetKey?: string | null +} + +interface ErrorBoundaryState { + hasError: boolean +} + +class ModelErrorBoundary extends Component { + state: ErrorBoundaryState = { hasError: false } + + static getDerivedStateFromError(): ErrorBoundaryState { + return { hasError: true } + } + + componentDidCatch(error: Error, info: ErrorInfo): void { + console.warn('[Viewer3D] Failed to load model:', error.message, info.componentStack) + } + + componentDidUpdate(prevProps: ErrorBoundaryProps): void { + if (prevProps.resetKey !== this.props.resetKey && this.state.hasError) { + this.setState({ hasError: false }) + } + } + + render(): ReactNode { + return this.state.hasError ? this.props.fallback : this.props.children + } +} + +function ModelLoadError(): JSX.Element { + return ( +
+ + + + + +

Model file not found

+
+ ) +} + // --------------------------------------------------------------------------- // MeshModel // --------------------------------------------------------------------------- @@ -230,75 +280,77 @@ export default function Viewer3D(): JSX.Element { } return ( -
- {!modelUrl && } - - - - - - - - {modelUrl && currentJob && ( - - - - - - + }> +
+ {!modelUrl && } + + + + + + + + {modelUrl && currentJob && ( + + + + + + + )} + + + + + {/* Left toolbar — visible only when a model is loaded */} + {modelUrl && ( + setAutoRotate((v) => !v)} + onScreenshot={handleScreenshot} + /> )} - - - - {/* Left toolbar — visible only when a model is loaded */} - {modelUrl && ( - setAutoRotate((v) => !v)} - onScreenshot={handleScreenshot} - /> - )} - - {/* Bottom-left stats overlay */} - {meshStats && ( -
-

- {meshStats.triangles.toLocaleString()} tri • {meshStats.vertices.toLocaleString()} verts -

-
- )} - - {/* Bottom-right hint */} - {modelUrl && ( -
-

Drag to rotate • Scroll to zoom

-
- )} -
+ {/* Bottom-left stats overlay */} + {meshStats && ( +
+

+ {meshStats.triangles.toLocaleString()} tri • {meshStats.vertices.toLocaleString()} verts +

+
+ )} + + {/* Bottom-right hint */} + {modelUrl && ( +
+

Drag to rotate • Scroll to zoom

+
+ )} +
+ ) } diff --git a/src/areas/generate/components/WorkspacePanel.tsx b/src/areas/generate/components/WorkspacePanel.tsx index 0e8d5c6..dc7ebb4 100644 --- a/src/areas/generate/components/WorkspacePanel.tsx +++ b/src/areas/generate/components/WorkspacePanel.tsx @@ -98,7 +98,10 @@ export default function WorkspacePanel(): JSX.Element { const jobs = activeCollection?.jobs ?? [] const handleDeleteConfirm = () => { - if (pendingDeleteId) removeFromWorkspace(pendingDeleteId) + if (pendingDeleteId) { + if (currentJob?.id === pendingDeleteId) setCurrentJob(null) + removeFromWorkspace(pendingDeleteId) + } setPendingDeleteId(null) } diff --git a/src/areas/workspace/WorkspacePage.tsx b/src/areas/workspace/WorkspacePage.tsx index be9cd79..2a789b3 100644 --- a/src/areas/workspace/WorkspacePage.tsx +++ b/src/areas/workspace/WorkspacePage.tsx @@ -28,7 +28,10 @@ export default function WorkspacePage(): JSX.Element { } const handleDeleteJobConfirm = () => { - if (pendingDeleteId) removeFromWorkspace(pendingDeleteId) + if (pendingDeleteId) { + if (currentJob?.id === pendingDeleteId) setCurrentJob(null) + removeFromWorkspace(pendingDeleteId) + } setPendingDeleteId(null) } From 0a48b71908f3e616ec8fe788cb98147fc390f3ba Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 15:17:54 +0100 Subject: [PATCH 16/20] feat(models): add show in explorer button to model cards --- electron/main/ipc-handlers.ts | 9 ++++++++- electron/preload/index.ts | 1 + src/areas/models/components/ModelCard.tsx | 15 +++++++++++++-- src/shared/types/electron.d.ts | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/electron/main/ipc-handlers.ts b/electron/main/ipc-handlers.ts index 68236e6..8083694 100644 --- a/electron/main/ipc-handlers.ts +++ b/electron/main/ipc-handlers.ts @@ -1,4 +1,4 @@ -import { ipcMain, BrowserWindow, dialog, app } from 'electron' +import { ipcMain, BrowserWindow, dialog, app, shell } from 'electron' import { autoUpdater } from 'electron-updater' import { join } from 'path' import { rm as rmAsync, readFile, writeFile, mkdir, readdir, rename, cp } from 'fs/promises' @@ -158,6 +158,13 @@ export function setupIpcHandlers(pythonBridge: PythonBridge, getWindow: WindowGe } }) + ipcMain.handle('model:showInFolder', (_, modelId: string) => { + const modelDir = join(getSettings(app.getPath('userData')).modelsDir, modelId) + if (existsSync(modelDir)) { + shell.openPath(modelDir) + } + }) + // Read local file → base64 (bypasses file:// restrictions in the renderer) ipcMain.handle('fs:readFileBase64', async (_, filePath: string) => { const buffer = await readFile(filePath) diff --git a/electron/preload/index.ts b/electron/preload/index.ts index 794f8d4..ba80015 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -71,6 +71,7 @@ contextBridge.exposeInMainWorld('electron', { download: (repoId: string, modelId: string, skipPrefixes?: string[]) => ipcRenderer.invoke('model:download', { repoId, modelId, skipPrefixes }), delete: (modelId: string) => ipcRenderer.invoke('model:delete', modelId), unloadAll: () => ipcRenderer.invoke('model:unloadAll'), + showInFolder: (modelId: string) => ipcRenderer.invoke('model:showInFolder', modelId), onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => { ipcRenderer.on('model:downloadProgress', (_event, data) => cb(data)) }, diff --git a/src/areas/models/components/ModelCard.tsx b/src/areas/models/components/ModelCard.tsx index 3397c7b..753b600 100644 --- a/src/areas/models/components/ModelCard.tsx +++ b/src/areas/models/components/ModelCard.tsx @@ -10,9 +10,20 @@ interface Props { export function ModelCard({ model, onDelete, onGenerate, disabled }: Props): JSX.Element { return ( -
+
+ {/* Open in explorer */} + + {/* Name */} -

+

{formatModelName(model.id)}

diff --git a/src/shared/types/electron.d.ts b/src/shared/types/electron.d.ts index 4f57891..708fa63 100644 --- a/src/shared/types/electron.d.ts +++ b/src/shared/types/electron.d.ts @@ -43,6 +43,7 @@ declare global { download: (repoId: string, modelId: string, skipPrefixes?: string[]) => Promise<{ success: boolean; error?: string }> delete: (modelId: string) => Promise<{ success: boolean; error?: string }> unloadAll: () => Promise<{ success: boolean; error?: string }> + showInFolder: (modelId: string) => Promise onProgress: (cb: (data: { modelId: string; percent: number; file?: string; fileIndex?: number; totalFiles?: number; status?: string }) => void) => void offProgress: () => void } From d2e12366eb2e3b036208cb5309a08a79bf63cf7e Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 16:42:19 +0100 Subject: [PATCH 17/20] fix(generate): improve memory release on unload and disable free-memory during generation --- api/routers/model.py | 10 +++ api/services/generators/base.py | 19 +++++ api/services/generators/hunyuan3d.py | 9 --- electron/main/python-bridge.ts | 11 ++- src/areas/generate/GeneratePage.tsx | 3 +- src/areas/models/ModelsPage.tsx | 112 +++++++++++++++++++++++---- 6 files changed, 140 insertions(+), 24 deletions(-) diff --git a/api/routers/model.py b/api/routers/model.py index 2e3bee0..6ababd5 100644 --- a/api/routers/model.py +++ b/api/routers/model.py @@ -43,6 +43,16 @@ async def switch_model(model_id: str): async def unload_all_models(): """Unloads all models from memory to free VRAM/RAM.""" generator_registry.unload_all() + # Force Python to release memory back to the OS + import gc + gc.collect() + try: + import ctypes, sys + if sys.platform == "win32": + k32 = ctypes.windll.kernel32 + k32.SetProcessWorkingSetSizeEx(k32.GetCurrentProcess(), -1, -1, 0) + except Exception: + pass return {"unloaded": True} diff --git a/api/services/generators/base.py b/api/services/generators/base.py index 0180f93..a23b01d 100644 --- a/api/services/generators/base.py +++ b/api/services/generators/base.py @@ -77,6 +77,25 @@ def load(self) -> None: def unload(self) -> None: """Release memory. Can be overridden if needed.""" self._model = None + import gc + gc.collect() + try: + import torch + if torch.cuda.is_available(): + torch.cuda.empty_cache() + except ImportError: + pass + # Force the OS to reclaim unused memory from this process + try: + import ctypes + import sys + if sys.platform == "win32": + kernel32 = ctypes.windll.kernel32 + kernel32.SetProcessWorkingSetSizeEx( + kernel32.GetCurrentProcess(), -1, -1, 0 + ) + except Exception: + pass def is_loaded(self) -> bool: return self._model is not None diff --git a/api/services/generators/hunyuan3d.py b/api/services/generators/hunyuan3d.py index 2a2b247..5f63570 100644 --- a/api/services/generators/hunyuan3d.py +++ b/api/services/generators/hunyuan3d.py @@ -69,15 +69,6 @@ def load(self) -> None: self._model = pipeline print(f"[Hunyuan3DGenerator] Loaded on {device}.") - def unload(self) -> None: - super().unload() - try: - import torch - if torch.cuda.is_available(): - torch.cuda.empty_cache() - except ImportError: - pass - # ------------------------------------------------------------------ # # Inference # ------------------------------------------------------------------ # diff --git a/electron/main/python-bridge.ts b/electron/main/python-bridge.ts index dde2d66..d04d028 100644 --- a/electron/main/python-bridge.ts +++ b/electron/main/python-bridge.ts @@ -16,6 +16,7 @@ export class PythonBridge { private ready = false private startPromise: Promise | null = null private getWindow: (() => BrowserWindow | null) | null = null + private intentionalStop = false setWindowGetter(fn: () => BrowserWindow | null): void { this.getWindow = fn @@ -78,7 +79,7 @@ export class PythonBridge { console.log('[PythonBridge] Process exited with code', code) this.ready = false this.process = null - if (wasReady) { + if (wasReady && !this.intentionalStop) { this.getWindow()?.webContents.send('python:crashed', { code }) } }) @@ -100,6 +101,14 @@ export class PythonBridge { console.log('[PythonBridge] Stopped') } + async restart(): Promise { + console.log('[PythonBridge] Restarting to free memory…') + this.intentionalStop = true + await this.stop() + this.intentionalStop = false + await this.start() + } + private emitTqdmLog(raw: string): void { if (/INFO/.test(raw)) return if (!raw.trim()) return diff --git a/src/areas/generate/GeneratePage.tsx b/src/areas/generate/GeneratePage.tsx index 9bb1747..4aee54c 100644 --- a/src/areas/generate/GeneratePage.tsx +++ b/src/areas/generate/GeneratePage.tsx @@ -63,8 +63,9 @@ export default function GeneratePage(): JSX.Element { {/* Free memory button — top-left overlay */}
@@ -348,17 +362,89 @@ export default function ModelsPage(): JSX.Element { )} {/* ── Confirm uninstall extension ──────────────────────────────────── */} - {uninstallTarget && ( - handleUninstallExtension(uninstallTarget)} - onCancel={() => setUninstallTarget(null)} - /> - )} + {uninstallTarget && (() => { + const ext = extensions.find((e) => e.id === uninstallTarget) + const installedModels = ext?.models.filter((v) => installedVariantIds.includes(v.id)) ?? [] + + return createPortal( +
{ if (e.target === e.currentTarget) { setUninstallTarget(null); setModelsToDelete(new Set()) } }} + > +
+
+
+
+
+ + + + + + +
+
+

+ Uninstall extension “{ext?.name ?? uninstallTarget}”? +

+

+ The extension folder will be deleted. +

+
+
+ + {installedModels.length > 0 && ( +
+

+ Also delete downloaded model weights: +

+ {installedModels.map((v) => { + const checked = modelsToDelete.has(v.id) + return ( + + ) + })} +
+ )} + +
+ + +
+
+
+
, + document.body + ) + })()}
) } From 57f936a82a39f86225ccbfa1fe2f3ffab9a37ed4 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 16:52:58 +0100 Subject: [PATCH 18/20] fix(workspace): prevent workspace model selection while a generation is running --- src/areas/generate/components/WorkspacePanel.tsx | 13 +++++++++---- src/areas/workspace/WorkspacePage.tsx | 3 +++ src/areas/workspace/components/WorkspaceCard.tsx | 9 ++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/areas/generate/components/WorkspacePanel.tsx b/src/areas/generate/components/WorkspacePanel.tsx index dc7ebb4..211127a 100644 --- a/src/areas/generate/components/WorkspacePanel.tsx +++ b/src/areas/generate/components/WorkspacePanel.tsx @@ -4,16 +4,19 @@ import { useCollectionsStore } from '@shared/stores/collectionsStore' import { ConfirmModal } from '@shared/components/ui' import { formatTime, formatDate } from '@shared/utils/format' -function ThumbnailItem({ job, isActive, onClick, onDelete }: { +function ThumbnailItem({ job, isActive, onClick, onDelete, disabled }: { job: GenerationJob isActive: boolean onClick: () => void onDelete: () => void + disabled?: boolean }): JSX.Element { return (
c.id === activeCollectionId) const jobs = activeCollection?.jobs ?? [] @@ -218,8 +222,9 @@ export default function WorkspacePanel(): JSX.Element { key={job.id} job={job} isActive={currentJob?.id === job.id} - onClick={() => handleJobClick(job)} - onDelete={() => setPendingDeleteId(job.id)} + onClick={() => !isGenerating && handleJobClick(job)} + onDelete={() => !isGenerating && setPendingDeleteId(job.id)} + disabled={isGenerating} /> ))}
diff --git a/src/areas/workspace/WorkspacePage.tsx b/src/areas/workspace/WorkspacePage.tsx index 2a789b3..fe3caf0 100644 --- a/src/areas/workspace/WorkspacePage.tsx +++ b/src/areas/workspace/WorkspacePage.tsx @@ -19,9 +19,11 @@ export default function WorkspacePage(): JSX.Element { const [addingCol, setAddingCol] = useState(false) const newColRef = useRef(null) + const isGenerating = currentJob?.status === 'uploading' || currentJob?.status === 'generating' const activeCollection = collections.find((c) => c.id === activeCollectionId) const handleJobClick = (job: GenerationJob) => { + if (isGenerating) return // Always restore the original mesh (before any optimization) setCurrentJob({ ...job, outputUrl: job.originalOutputUrl ?? job.outputUrl }) navigate('generate') @@ -146,6 +148,7 @@ export default function WorkspacePage(): JSX.Element { isActive={currentJob?.id === job.id} onClick={() => handleJobClick(job)} onDelete={() => setPendingDeleteId(job.id)} + disabled={isGenerating} /> ))}
diff --git a/src/areas/workspace/components/WorkspaceCard.tsx b/src/areas/workspace/components/WorkspaceCard.tsx index 9344ce5..8549665 100644 --- a/src/areas/workspace/components/WorkspaceCard.tsx +++ b/src/areas/workspace/components/WorkspaceCard.tsx @@ -6,21 +6,24 @@ interface Props { isActive: boolean onClick: () => void onDelete: () => void + disabled?: boolean } -export function WorkspaceCard({ job, isActive, onClick, onDelete }: Props): JSX.Element { +export function WorkspaceCard({ job, isActive, onClick, onDelete, disabled }: Props): JSX.Element { const modelLabel = job.modelId ? (MODEL_LABEL[job.modelId] ?? job.modelId) : null return (
{/* Thumbnail */}
From 0f8927687948feb4f687784fa5dfbb26e4e372d6 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 18:39:48 +0100 Subject: [PATCH 19/20] fix release pipeline --- .github/workflows/release.yml | 59 +++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 95ec6d9..b55f858 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,8 +9,26 @@ permissions: contents: write jobs: + # ─── Create a single release ──────────────────────────────────────────────── + create-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create release + run: | + gh release create ${{ github.ref_name }} \ + --title "Modly Beta ${{ github.ref_name }}" \ + --generate-notes \ + --latest \ + --repo ${{ github.repository }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # ─── Windows build ──────────────────────────────────────────────────────────── build-windows: + needs: create-release runs-on: windows-latest steps: - name: Checkout @@ -28,25 +46,27 @@ jobs: - name: Download Python embed run: node scripts/download-python-embed.js - - name: Create draft release - run: | - gh release create ${{ github.ref_name }} \ - --title "Modly Beta ${{ github.ref_name }}" \ - --generate-notes \ - --draft \ - --repo ${{ github.repository }} - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Build & Package (Windows) - run: npx electron-vite build && npx electron-builder --win --publish always + shell: bash + run: npx electron-vite build && npx electron-builder --win --publish never env: CSC_IDENTITY_AUTO_DISCOVERY: false + + - name: Upload assets to release + shell: bash + run: | + gh release upload ${{ github.ref_name }} \ + dist/*.exe \ + dist/*.exe.blockmap \ + dist/latest.yml \ + --repo ${{ github.repository }} \ + --clobber + env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} # ─── Linux build ───────────────────────────────────────────────────────────── build-linux: - needs: build-windows + needs: create-release runs-on: ubuntu-latest steps: - name: Checkout @@ -61,10 +81,15 @@ jobs: - name: Install dependencies run: npm install - - name: Install Linux build dependencies - run: sudo apt-get install -y rpm - - name: Build & Package (Linux) - run: npx electron-vite build && npx electron-builder --linux --publish always + run: npx electron-vite build && npx electron-builder --linux --publish never + + - name: Upload assets to release + run: | + gh release upload ${{ github.ref_name }} \ + dist/*.AppImage \ + dist/latest-linux.yml \ + --repo ${{ github.repository }} \ + --clobber env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file From 081b2c94f5579fd6743ea49563841ffa4d5cbed0 Mon Sep 17 00:00:00 2001 From: Lightning Pixel Date: Sun, 22 Mar 2026 18:42:04 +0100 Subject: [PATCH 20/20] dump version 0.2.0 --- api/main.py | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/api/main.py b/api/main.py index 350b143..c102087 100644 --- a/api/main.py +++ b/api/main.py @@ -23,7 +23,7 @@ async def lifespan(app: FastAPI): app = FastAPI( title="Modly API", - version="0.1.3", + version="0.2.0", lifespan=lifespan, ) diff --git a/package.json b/package.json index 8cceeef..2695367 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "modly", - "version": "0.1.3", + "version": "0.2.0", "description": "Local AI-powered 3D mesh generation from images", "main": "./out/main/index.js", "author": "Modly",