diff --git a/.github/workflows/desktop-artifacts.yml b/.github/workflows/desktop-artifacts.yml
new file mode 100644
index 00000000..290b6cc1
--- /dev/null
+++ b/.github/workflows/desktop-artifacts.yml
@@ -0,0 +1,66 @@
+name: Desktop Artifacts
+
+on:
+ pull_request:
+ branches: [main]
+ paths:
+ - 'desktop/**'
+ - 'server/**'
+ - 'web/**'
+ - 'shared/**'
+ - 'docs/DESKTOP-RELEASE.md'
+ - 'scripts/desktop-after-pack.mjs'
+ - 'scripts/prepare-desktop-release.mjs'
+ - 'package.json'
+ - 'pnpm-lock.yaml'
+ - 'pnpm-workspace.yaml'
+ - '.github/workflows/desktop-artifacts.yml'
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ NODE_VERSION: '22'
+
+jobs:
+ mac-unsigned:
+ name: Unsigned macOS Artifact
+ runs-on: macos-15
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: pnpm/action-setup@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build workspace
+ run: pnpm build
+
+ - name: Prepare desktop runtime payload
+ run: pnpm --filter @veritas-kanban/desktop package:prepare
+
+ - name: Build unsigned macOS DMG and update metadata
+ working-directory: desktop
+ env:
+ CSC_IDENTITY_AUTO_DISCOVERY: 'false'
+ run: pnpm exec electron-builder --mac dmg zip --publish never --config.mac.identity=null --config.mac.notarize=false
+
+ - name: Upload desktop artifacts
+ uses: actions/upload-artifact@v5
+ with:
+ name: veritas-kanban-mac-unsigned
+ path: |
+ desktop/release/*.dmg
+ desktop/release/*.zip
+ desktop/release/*.yml
+ desktop/release/*.blockmap
+ if-no-files-found: error
+ retention-days: 14
diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml
new file mode 100644
index 00000000..92a36672
--- /dev/null
+++ b/.github/workflows/desktop-release.yml
@@ -0,0 +1,78 @@
+name: Desktop Release
+
+on:
+ workflow_dispatch:
+ inputs:
+ channel:
+ description: Update channel to publish.
+ required: true
+ default: stable
+ type: choice
+ options:
+ - stable
+ - beta
+ - dev
+ release:
+ types: [published]
+
+permissions:
+ contents: write
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: false
+
+env:
+ NODE_VERSION: '22'
+ VERITAS_UPDATE_CHANNEL: ${{ github.event.inputs.channel || 'stable' }}
+
+jobs:
+ mac-signed:
+ name: Signed and Notarized macOS Artifact
+ runs-on: macos-15
+ steps:
+ - uses: actions/checkout@v6
+
+ - uses: pnpm/action-setup@v6
+
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION }}
+ cache: pnpm
+
+ - name: Verify signing secrets are configured
+ env:
+ CSC_LINK: ${{ secrets.MACOS_CSC_LINK }}
+ CSC_KEY_PASSWORD: ${{ secrets.MACOS_CSC_KEY_PASSWORD }}
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ run: |
+ missing=0
+ for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
+ if [ -z "${!name}" ]; then
+ echo "::error::$name is required for signed/notarized desktop releases"
+ missing=1
+ fi
+ done
+ exit "$missing"
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build workspace
+ run: pnpm build
+
+ - name: Prepare desktop runtime payload
+ run: pnpm --filter @veritas-kanban/desktop package:prepare
+
+ - name: Build, sign, notarize, and publish macOS artifacts
+ working-directory: desktop
+ env:
+ GH_TOKEN: ${{ github.token }}
+ CSC_LINK: ${{ secrets.MACOS_CSC_LINK }}
+ CSC_KEY_PASSWORD: ${{ secrets.MACOS_CSC_KEY_PASSWORD }}
+ APPLE_ID: ${{ secrets.APPLE_ID }}
+ APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
+ APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
+ run: pnpm exec electron-builder --mac dmg zip --publish always
diff --git a/.gitignore b/.gitignore
index 3e4c0ea0..1b336992 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,8 @@ tasks/archive-attachments/
.veritas-kanban/*
!.veritas-kanban/.gitkeep
.veritas-desktop-dev/
+desktop/.desktop-release/
+desktop/release/
# Historical broken config data (should never have been tracked)
.veritas-kanban.broken/
diff --git a/desktop/README.md b/desktop/README.md
index 066b31be..cb3ea54c 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -78,6 +78,15 @@ with generic copy while preserving the durable target for click-through.
Window size, position, and maximized state are persisted per profile/workspace
in `config/window-state.json`.
+## Release Packaging
+
+Unsigned PR artifacts, signed/notarized release artifacts, update metadata, and
+macOS smoke steps are documented in
+[`docs/DESKTOP-RELEASE.md`](../docs/DESKTOP-RELEASE.md). Use the root
+`desktop:package:mac:unsigned` script for local unsigned DMG/ZIP validation and
+`desktop:release:mac` only when Apple signing/notarization credentials are
+configured.
+
## Production Scaffold
`pnpm desktop:build` compiles the Electron main, preload, and fallback renderer.
diff --git a/desktop/package.json b/desktop/package.json
index 7a383c7c..28314bc0 100644
--- a/desktop/package.json
+++ b/desktop/package.json
@@ -11,17 +11,23 @@
"dev": "electron-vite dev",
"dev:fresh": "VERITAS_DESKTOP_PROFILE=fresh electron-vite dev",
"build": "electron-vite build",
+ "package:prepare": "node ../scripts/prepare-desktop-release.mjs",
+ "package:mac:dir": "pnpm package:prepare && electron-builder --mac dir --publish never --config.mac.identity=null --config.mac.notarize=false",
+ "package:mac:unsigned": "pnpm package:prepare && electron-builder --mac dmg zip --publish never --config.mac.identity=null --config.mac.notarize=false",
+ "release:mac": "pnpm package:prepare && electron-builder --mac dmg zip --publish always",
"typecheck": "tsc --noEmit",
"lint": "eslint src electron.vite.config.ts --ext .ts",
"test": "vitest run --config vitest.config.ts",
"clean": "rm -rf dist out .veritas-desktop-dev"
},
"dependencies": {
- "@veritas-kanban/shared": "workspace:*"
+ "@veritas-kanban/shared": "workspace:*",
+ "electron-updater": "^6.8.3"
},
"devDependencies": {
"@types/node": "^25.7.0",
"electron": "^39.2.6",
+ "electron-builder": "^26.8.1",
"electron-vite": "^5.0.0",
"typescript": "^6.0.3",
"vite": "^7.2.7",
@@ -30,11 +36,73 @@
"build": {
"appId": "io.digitalmeld.veritas-kanban",
"productName": "Veritas Kanban",
+ "artifactName": "${productName}-${version}-${os}-${arch}.${ext}",
+ "asar": true,
+ "afterPack": "../scripts/desktop-after-pack.mjs",
"directories": {
- "buildResources": "resources"
+ "buildResources": "resources",
+ "output": "release"
},
+ "files": [
+ "out/**/*",
+ "resources/**/*",
+ "package.json",
+ "node_modules/**/*"
+ ],
+ "extraResources": [
+ {
+ "from": ".desktop-release/server",
+ "to": "server"
+ },
+ {
+ "from": ".desktop-release/web",
+ "to": "web"
+ }
+ ],
+ "protocols": [
+ {
+ "name": "Veritas Kanban",
+ "schemes": [
+ "veritas"
+ ],
+ "role": "Viewer"
+ }
+ ],
+ "generateUpdatesFilesForAllChannels": true,
"mac": {
- "category": "public.app-category.productivity"
- }
+ "category": "public.app-category.productivity",
+ "target": [
+ "dmg",
+ "zip"
+ ],
+ "hardenedRuntime": true,
+ "gatekeeperAssess": false,
+ "entitlements": "resources/entitlements.mac.plist",
+ "entitlementsInherit": "resources/entitlements.mac.inherit.plist",
+ "notarize": true
+ },
+ "dmg": {
+ "sign": false,
+ "contents": [
+ {
+ "x": 130,
+ "y": 220
+ },
+ {
+ "x": 410,
+ "y": 220,
+ "type": "link",
+ "path": "/Applications"
+ }
+ ]
+ },
+ "publish": [
+ {
+ "provider": "github",
+ "owner": "BradGroux",
+ "repo": "veritas-kanban",
+ "releaseType": "draft"
+ }
+ ]
}
}
diff --git a/desktop/resources/entitlements.mac.inherit.plist b/desktop/resources/entitlements.mac.inherit.plist
new file mode 100644
index 00000000..6dcade08
--- /dev/null
+++ b/desktop/resources/entitlements.mac.inherit.plist
@@ -0,0 +1,14 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-library-validation
+
+ com.apple.security.inherit
+
+
+
diff --git a/desktop/resources/entitlements.mac.plist b/desktop/resources/entitlements.mac.plist
new file mode 100644
index 00000000..05d489e9
--- /dev/null
+++ b/desktop/resources/entitlements.mac.plist
@@ -0,0 +1,12 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+ com.apple.security.cs.disable-library-validation
+
+
+
diff --git a/desktop/src/main/__tests__/commands.test.ts b/desktop/src/main/__tests__/commands.test.ts
index ad88988b..3d2af955 100644
--- a/desktop/src/main/__tests__/commands.test.ts
+++ b/desktop/src/main/__tests__/commands.test.ts
@@ -8,7 +8,10 @@ import {
} from '../commands.js';
import type { DesktopRuntime } from '../runtime.js';
import type { DesktopStatusSnapshot } from '../types.js';
-import { DESKTOP_COMMAND_NAMES } from '../../shared/desktop-bridge-contracts.js';
+import {
+ DESKTOP_COMMAND_NAMES,
+ type DesktopUpdateStatus,
+} from '../../shared/desktop-bridge-contracts.js';
function status(): DesktopStatusSnapshot {
return {
@@ -37,6 +40,15 @@ function status(): DesktopStatusSnapshot {
};
}
+function updateStatus(state: DesktopUpdateStatus['state'] = 'idle'): DesktopUpdateStatus {
+ return {
+ state,
+ currentVersion: '4.3.2',
+ channel: 'stable',
+ checkedAt: '2026-05-31T00:00:00.000Z',
+ };
+}
+
function dispatcher() {
const runtime = {
snapshot: vi.fn(status),
@@ -46,7 +58,9 @@ function dispatcher() {
openPath: vi.fn(async () => ''),
} as unknown as Shell;
const sendRendererCommand = vi.fn();
- const sendUpdateStatus = vi.fn();
+ const checkForUpdates = vi.fn(async () => updateStatus('idle'));
+ const downloadUpdate = vi.fn(async () => updateStatus('ready'));
+ const installUpdate = vi.fn(() => updateStatus('ready'));
const showTestNotification = vi.fn();
const copyRedactedDiagnostics = vi.fn();
@@ -54,7 +68,9 @@ function dispatcher() {
runtime,
shell,
sendRendererCommand,
- sendUpdateStatus,
+ checkForUpdates,
+ downloadUpdate,
+ installUpdate,
showTestNotification,
copyRedactedDiagnostics,
dispatcher: new DesktopCommandDispatcher({
@@ -62,7 +78,9 @@ function dispatcher() {
shell,
quit: vi.fn(),
sendRendererCommand,
- sendUpdateStatus,
+ checkForUpdates,
+ downloadUpdate,
+ installUpdate,
showTestNotification,
copyRedactedDiagnostics,
}),
@@ -107,6 +125,8 @@ describe('desktop command registry', () => {
});
await harness.dispatcher.dispatch(createDesktopCommandRequest('open-logs', 'menu'));
await harness.dispatcher.dispatch(createDesktopCommandRequest('check-for-updates', 'menu'));
+ await harness.dispatcher.dispatch(createDesktopCommandRequest('download-update', 'menu'));
+ await harness.dispatcher.dispatch(createDesktopCommandRequest('install-update', 'menu'));
await harness.dispatcher.dispatch(createDesktopCommandRequest('test-notification', 'menu'));
await harness.dispatcher.dispatch(
createDesktopCommandRequest('copy-redacted-diagnostics', 'menu')
@@ -114,11 +134,9 @@ describe('desktop command registry', () => {
expect(harness.runtime.restartLocalServer).toHaveBeenCalledTimes(1);
expect(harness.shell.openPath).toHaveBeenCalledWith('/tmp/veritas/logs');
- expect(harness.sendUpdateStatus).toHaveBeenCalledWith(
- expect.objectContaining({
- state: 'unsupported',
- })
- );
+ expect(harness.checkForUpdates).toHaveBeenCalledTimes(1);
+ expect(harness.downloadUpdate).toHaveBeenCalledTimes(1);
+ expect(harness.installUpdate).toHaveBeenCalledTimes(1);
expect(harness.showTestNotification).toHaveBeenCalledTimes(1);
expect(harness.copyRedactedDiagnostics).toHaveBeenCalledWith(status());
});
diff --git a/desktop/src/main/__tests__/lifecycle.test.ts b/desktop/src/main/__tests__/lifecycle.test.ts
index 23048743..34ad59fd 100644
--- a/desktop/src/main/__tests__/lifecycle.test.ts
+++ b/desktop/src/main/__tests__/lifecycle.test.ts
@@ -47,11 +47,30 @@ describe('desktop lifecycle config', () => {
expect(env.VERITAS_ADMIN_KEY).toBe('desktop-keychain-admin-key');
expect(env.VERITAS_JWT_SECRET).toBe('desktop-keychain-jwt-secret');
expect(env.VERITAS_STORAGE).toBe('sqlite');
+ expect(env.VERITAS_DESKTOP_RUNTIME).toBe('0');
+ expect(env.DATA_DIR).toBe('/tmp/veritas-desktop/data');
expect(env.VERITAS_DATA_DIR).toBe('/tmp/veritas-desktop/data');
expect(env.VERITAS_AUTH_ENABLED).toBe('false');
expect(env.CORS_ORIGINS).toContain('http://127.0.0.1:39124');
});
+ it('runs packaged server with the Electron binary in Node mode from resources', () => {
+ const configs = createManagedProcessConfigs({
+ ...options(),
+ isPackaged: true,
+ resourcesPath: '/Applications/Veritas Kanban.app/Contents/Resources',
+ });
+
+ expect(configs).toHaveLength(1);
+ expect(configs[0]?.args).toEqual([
+ '/Applications/Veritas Kanban.app/Contents/Resources/server/dist/index.js',
+ ]);
+ expect(configs[0]?.cwd).toBe('/Applications/Veritas Kanban.app/Contents/Resources/server');
+ expect(configs[0]?.env.ELECTRON_RUN_AS_NODE).toBe('1');
+ expect(configs[0]?.env.VERITAS_DESKTOP_RUNTIME).toBe('1');
+ expect(configs[0]?.env.VERITAS_AUTH_ENABLED).toBe('true');
+ });
+
it('points web dev proxies at the selected server port', () => {
const env = buildWebEnvironment(options());
diff --git a/desktop/src/main/__tests__/menu.test.ts b/desktop/src/main/__tests__/menu.test.ts
index d885ed5e..2863ebaf 100644
--- a/desktop/src/main/__tests__/menu.test.ts
+++ b/desktop/src/main/__tests__/menu.test.ts
@@ -2,6 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
import { createDesktopMenuTemplate } from '../menu.js';
import type { DesktopStatusSnapshot } from '../types.js';
+import type { DesktopUpdateStatus } from '../../shared/desktop-bridge-contracts.js';
function status(state: DesktopStatusSnapshot['server']['state'] = 'ready'): DesktopStatusSnapshot {
return {
@@ -30,6 +31,15 @@ function status(state: DesktopStatusSnapshot['server']['state'] = 'ready'): Desk
};
}
+function updateStatus(state: DesktopUpdateStatus['state']): DesktopUpdateStatus {
+ return {
+ state,
+ currentVersion: '4.3.2',
+ channel: 'stable',
+ checkedAt: '2026-05-31T00:00:00.000Z',
+ };
+}
+
describe('desktop native menu', () => {
it('exposes common actions with keyboard shortcuts', () => {
const dispatch = vi.fn();
@@ -65,4 +75,21 @@ describe('desktop native menu', () => {
expect(externalTest?.enabled).toBe(false);
});
+
+ it('keeps update install actions tied to updater state', () => {
+ const appMenu = createDesktopMenuTemplate({
+ status: status(),
+ updateStatus: updateStatus('available'),
+ dispatch: vi.fn(),
+ }).find((item) => item.label === 'Veritas Kanban');
+ const downloadUpdate = Array.isArray(appMenu?.submenu)
+ ? appMenu.submenu.find((item) => item.label === 'Download Update')
+ : null;
+ const installUpdate = Array.isArray(appMenu?.submenu)
+ ? appMenu.submenu.find((item) => item.label === 'Install Update')
+ : null;
+
+ expect(downloadUpdate?.enabled).toBe(true);
+ expect(installUpdate?.enabled).toBe(false);
+ });
});
diff --git a/desktop/src/main/__tests__/process-supervisor.test.ts b/desktop/src/main/__tests__/process-supervisor.test.ts
new file mode 100644
index 00000000..d7be949d
--- /dev/null
+++ b/desktop/src/main/__tests__/process-supervisor.test.ts
@@ -0,0 +1,73 @@
+import { mkdtemp } from 'node:fs/promises';
+import os from 'node:os';
+import path from 'node:path';
+
+import { describe, expect, it } from 'vitest';
+
+import { ProcessSupervisor } from '../process-supervisor.js';
+import type { DesktopProcessState, ManagedProcessConfig } from '../types.js';
+
+async function createConfig(args: string[]): Promise {
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), 'veritas-process-supervisor-'));
+
+ return {
+ name: 'server',
+ command: process.execPath,
+ args,
+ cwd: process.cwd(),
+ env: process.env,
+ logFile: path.join(tempDir, 'server.log'),
+ };
+}
+
+function waitForState(
+ supervisor: ProcessSupervisor,
+ state: DesktopProcessState
+): Promise> {
+ const snapshot = supervisor.snapshot();
+ if (snapshot.state === state) {
+ return Promise.resolve(snapshot);
+ }
+
+ return new Promise((resolve, reject) => {
+ const timeout = setTimeout(() => {
+ supervisor.off('state', onState);
+ reject(new Error(`Timed out waiting for ${state}`));
+ }, 2_000);
+
+ const onState = (next: ReturnType) => {
+ if (next.state === state) {
+ clearTimeout(timeout);
+ supervisor.off('state', onState);
+ resolve(next);
+ }
+ };
+
+ supervisor.on('state', onState);
+ });
+}
+
+describe('ProcessSupervisor', () => {
+ it('fails when a process exits before readiness, even with code 0', async () => {
+ const supervisor = new ProcessSupervisor(await createConfig(['-e', 'process.exit(0)']));
+
+ const failed = waitForState(supervisor, 'failed');
+ await supervisor.start();
+
+ const snapshot = await failed;
+ expect(snapshot.lastError).toContain('server exited before becoming ready with code 0');
+ });
+
+ it('treats a clean exit after readiness as stopped', async () => {
+ const supervisor = new ProcessSupervisor(
+ await createConfig(['-e', 'setTimeout(() => process.exit(0), 50)'])
+ );
+
+ const stopped = waitForState(supervisor, 'stopped');
+ await supervisor.start();
+ supervisor.markReady();
+
+ const snapshot = await stopped;
+ expect(snapshot.lastError).toBeNull();
+ });
+});
diff --git a/desktop/src/main/__tests__/updates.test.ts b/desktop/src/main/__tests__/updates.test.ts
new file mode 100644
index 00000000..b048bea4
--- /dev/null
+++ b/desktop/src/main/__tests__/updates.test.ts
@@ -0,0 +1,128 @@
+import { describe, expect, it, vi } from 'vitest';
+
+import {
+ DesktopUpdateService,
+ resolveDesktopUpdateChannel,
+ type DesktopUpdateAdapter,
+ type DesktopUpdateAdapterConfigureOptions,
+} from '../updates.js';
+
+type UpdateListener = (...args: unknown[]) => void;
+
+class FakeUpdateAdapter implements DesktopUpdateAdapter {
+ configure = vi.fn((options: DesktopUpdateAdapterConfigureOptions) => {
+ this.config = options;
+ });
+ checkForUpdates = vi.fn(async () => null);
+ downloadUpdate = vi.fn(async () => []);
+ quitAndInstall = vi.fn();
+ active = true;
+ config: DesktopUpdateAdapterConfigureOptions | null = null;
+ private readonly listeners = new Map();
+
+ on(event: string, listener: UpdateListener): void {
+ const existing = this.listeners.get(event) ?? [];
+ this.listeners.set(event, [...existing, listener]);
+ }
+
+ emit(event: string, ...args: unknown[]): void {
+ for (const listener of this.listeners.get(event) ?? []) {
+ listener(...args);
+ }
+ }
+
+ isUpdaterActive(): boolean {
+ return this.active;
+ }
+}
+
+function service(adapter = new FakeUpdateAdapter()) {
+ const emitStatus = vi.fn();
+ return {
+ adapter,
+ emitStatus,
+ service: new DesktopUpdateService({
+ adapter,
+ packaged: true,
+ currentVersion: '4.3.2',
+ channel: 'stable',
+ now: () => new Date('2026-05-31T00:00:00.000Z'),
+ emitStatus,
+ }),
+ };
+}
+
+describe('desktop update service', () => {
+ it('configures updater for manual download and stable channel release checks', () => {
+ const harness = service();
+
+ expect(harness.adapter.configure).toHaveBeenCalledWith({
+ allowPrerelease: false,
+ autoDownload: false,
+ autoInstallOnAppQuit: false,
+ channel: 'stable',
+ forceDevUpdateConfig: false,
+ });
+ expect(harness.service.snapshot()).toMatchObject({
+ state: 'idle',
+ currentVersion: '4.3.2',
+ channel: 'stable',
+ });
+ });
+
+ it('emits available, downloading, and ready states from updater events', async () => {
+ const harness = service();
+
+ await harness.service.checkForUpdates();
+ harness.adapter.emit('update-available', { version: '4.3.3' });
+ harness.adapter.emit('download-progress', { percent: 55.2 });
+ harness.adapter.emit('update-downloaded', { version: '4.3.3' });
+
+ expect(harness.emitStatus).toHaveBeenCalledWith(
+ expect.objectContaining({ state: 'available', availableVersion: '4.3.3' })
+ );
+ expect(harness.emitStatus).toHaveBeenCalledWith(
+ expect.objectContaining({ state: 'downloading', detail: '55% downloaded.' })
+ );
+ expect(harness.service.snapshot()).toMatchObject({
+ state: 'ready',
+ availableVersion: '4.3.3',
+ });
+ });
+
+ it('keeps dev builds unsupported unless force dev update config is enabled', async () => {
+ const adapter = new FakeUpdateAdapter();
+ const updateService = new DesktopUpdateService({
+ adapter,
+ packaged: false,
+ currentVersion: '4.3.2',
+ channel: 'dev',
+ });
+
+ await expect(updateService.checkForUpdates()).resolves.toMatchObject({
+ state: 'unsupported',
+ });
+ expect(adapter.checkForUpdates).not.toHaveBeenCalled();
+ });
+
+ it('redacts sensitive update errors before publishing status', async () => {
+ const harness = service();
+ harness.adapter.checkForUpdates.mockRejectedValueOnce(
+ new Error('download failed token=abc123 path=/Users/bradgroux/private')
+ );
+
+ await harness.service.checkForUpdates();
+
+ expect(harness.service.snapshot()).toMatchObject({
+ state: 'failed',
+ detail: 'download failed token=[redacted] path=/Users/[redacted]/private',
+ });
+ });
+
+ it('resolves stable, beta, and dev channels conservatively', () => {
+ expect(resolveDesktopUpdateChannel(undefined, '4.3.2', true)).toBe('stable');
+ expect(resolveDesktopUpdateChannel(undefined, '5.0.0-beta.1', true)).toBe('beta');
+ expect(resolveDesktopUpdateChannel('dev', '4.3.2', true)).toBe('dev');
+ expect(resolveDesktopUpdateChannel(undefined, '4.3.2', false)).toBe('dev');
+ });
+});
diff --git a/desktop/src/main/bridge.ts b/desktop/src/main/bridge.ts
index 6e9375bf..ecfae22a 100644
--- a/desktop/src/main/bridge.ts
+++ b/desktop/src/main/bridge.ts
@@ -4,6 +4,7 @@ import { DESKTOP_APP_ID, DESKTOP_APP_NAME } from './app-metadata.js';
import type { DesktopCommandDispatcher } from './commands.js';
import type { DesktopAppInfo } from './types.js';
import type { DesktopRuntime } from './runtime.js';
+import type { DesktopUpdateService } from './updates.js';
import {
createDesktopSetupDiagnostics,
createDesktopSupportSnapshot,
@@ -36,7 +37,8 @@ export function createDesktopBridgeHandlers(
runtime: DesktopRuntime,
shell: Shell,
packaged: boolean,
- commandDispatcher?: DesktopCommandDispatcher
+ commandDispatcher?: DesktopCommandDispatcher,
+ updateService?: DesktopUpdateService
): DesktopBridgeHandlerMap {
const appInfo = (): DesktopAppInfo => ({
name: DESKTOP_APP_NAME,
@@ -65,13 +67,14 @@ export function createDesktopBridgeHandlers(
return runtime.restartLocalServer();
},
getSupportSnapshot: () => createDesktopSupportSnapshot(runtime.snapshot()),
- getUpdateStatus: () => ({
- state: 'unsupported',
- currentVersion: appInfo().version,
- channel: packaged ? 'stable' : 'dev',
- checkedAt: new Date().toISOString(),
- detail: 'Updater implementation is tracked in the desktop release pipeline issue.',
- }),
+ getUpdateStatus: () =>
+ updateService?.snapshot() ?? {
+ state: 'unsupported',
+ currentVersion: appInfo().version,
+ channel: packaged ? 'stable' : 'dev',
+ checkedAt: new Date().toISOString(),
+ detail: 'Updater service is not initialized.',
+ },
dispatchCommand: (request) => {
const command = validateDesktopCommandDispatchRequest(request);
return commandDispatcher
@@ -128,9 +131,16 @@ export function registerDesktopBridge(
runtime: DesktopRuntime,
shell: Shell,
packaged: boolean,
- commandDispatcher?: DesktopCommandDispatcher
+ commandDispatcher?: DesktopCommandDispatcher,
+ updateService?: DesktopUpdateService
): void {
- const handlers = createDesktopBridgeHandlers(runtime, shell, packaged, commandDispatcher);
+ const handlers = createDesktopBridgeHandlers(
+ runtime,
+ shell,
+ packaged,
+ commandDispatcher,
+ updateService
+ );
for (const method of DESKTOP_BRIDGE_METHOD_NAMES) {
const definition = DESKTOP_BRIDGE_METHODS[method];
diff --git a/desktop/src/main/commands.ts b/desktop/src/main/commands.ts
index 9df7bba6..5f91e783 100644
--- a/desktop/src/main/commands.ts
+++ b/desktop/src/main/commands.ts
@@ -19,6 +19,8 @@ export type DesktopCommandNativeAction =
| 'show-diagnostics'
| 'create-debug-bundle'
| 'check-updates'
+ | 'download-update'
+ | 'install-update'
| 'test-notification'
| 'test-external-delivery'
| 'copy-diagnostics'
@@ -74,6 +76,10 @@ function commandLabel(name: DesktopCommandName): string {
return 'Create Debug Bundle';
case 'check-for-updates':
return 'Check for Updates';
+ case 'download-update':
+ return 'Download Update';
+ case 'install-update':
+ return 'Install Update';
case 'test-notification':
return 'Test Local Notification';
case 'test-squad-webhook':
@@ -116,6 +122,10 @@ function commandNativeAction(name: DesktopCommandName): DesktopCommandNativeActi
return 'create-debug-bundle';
case 'check-for-updates':
return 'check-updates';
+ case 'download-update':
+ return 'download-update';
+ case 'install-update':
+ return 'install-update';
case 'test-notification':
return 'test-notification';
case 'test-squad-webhook':
@@ -138,7 +148,9 @@ export interface DesktopCommandDispatcherOptions {
shell: Shell;
quit(): void;
sendRendererCommand(command: DesktopCommandDispatchRequest): void;
- sendUpdateStatus(status: DesktopUpdateStatus): void;
+ checkForUpdates(): Promise;
+ downloadUpdate(): Promise;
+ installUpdate(): DesktopUpdateStatus;
showTestNotification(): void;
copyRedactedDiagnostics(status: DesktopStatusSnapshot): void;
}
@@ -166,13 +178,13 @@ export class DesktopCommandDispatcher {
this.options.sendRendererCommand(request);
return accepted(request, 'renderer');
case 'check-updates':
- this.options.sendUpdateStatus({
- state: 'unsupported',
- currentVersion: process.env.npm_package_version || '0.0.0',
- channel: 'dev',
- checkedAt: new Date().toISOString(),
- detail: 'Updater implementation is tracked in the desktop release pipeline issue.',
- });
+ await this.options.checkForUpdates();
+ return accepted(request, 'desktop');
+ case 'download-update':
+ await this.options.downloadUpdate();
+ return accepted(request, 'desktop');
+ case 'install-update':
+ this.options.installUpdate();
return accepted(request, 'desktop');
case 'test-notification':
this.options.showTestNotification();
diff --git a/desktop/src/main/index.ts b/desktop/src/main/index.ts
index 4d1c0b58..cf8e0de3 100644
--- a/desktop/src/main/index.ts
+++ b/desktop/src/main/index.ts
@@ -1,6 +1,7 @@
import { app, BrowserWindow, clipboard, ipcMain, Notification, safeStorage, shell } from 'electron';
import path from 'node:path';
import { mkdirSync } from 'node:fs';
+import { createRequire } from 'node:module';
import { DESKTOP_APP_ID, DESKTOP_APP_NAME, DESKTOP_MIN_WINDOW } from './app-metadata.js';
import { registerDesktopBridge } from './bridge.js';
@@ -13,9 +14,15 @@ import { findAvailablePort } from './ports.js';
import { DesktopRuntime } from './runtime.js';
import { DesktopSecretStore } from './secrets.js';
import { statusPageUrl } from './status-page.js';
+import {
+ DesktopUpdateService,
+ ElectronAutoUpdaterAdapter,
+ resolveDesktopUpdateChannel,
+} from './updates.js';
import {
DESKTOP_BRIDGE_EVENTS,
redactDesktopBridgeValue,
+ type DesktopUpdateStatus,
} from '../shared/desktop-bridge-contracts.js';
import {
applyDesktopWindowState,
@@ -25,9 +32,13 @@ import {
type DesktopWindowState,
} from './window-state.js';
+const require = createRequire(import.meta.url);
+const { autoUpdater } = require('electron-updater') as typeof import('electron-updater');
+
let mainWindow: BrowserWindow | null = null;
let runtime: DesktopRuntime | null = null;
let commandDispatcher: DesktopCommandDispatcher | null = null;
+let updateService: DesktopUpdateService | null = null;
let windowStatePaths: ReturnType | null = null;
let quitting = false;
let shutdownStarted = false;
@@ -133,6 +144,32 @@ function flushPendingDeepLinks(): void {
}
}
+function refreshDesktopMenu(): void {
+ if (!runtime || !commandDispatcher) {
+ return;
+ }
+
+ configureDesktopMenu({
+ status: runtime.snapshot(),
+ updateStatus: updateService?.snapshot(),
+ dispatch: (command) => {
+ if (commandDispatcher) {
+ dispatchDesktopMenuCommand(commandDispatcher, command);
+ }
+ },
+ });
+}
+
+function updateServiceFallback(packaged: boolean): DesktopUpdateStatus {
+ return {
+ state: 'unsupported',
+ currentVersion: app.getVersion(),
+ channel: packaged ? 'stable' : 'dev',
+ checkedAt: new Date().toISOString(),
+ detail: 'Updater service is not initialized.',
+ };
+}
+
async function boot(): Promise {
app.setName(DESKTOP_APP_NAME);
app.setAppUserModelId(DESKTOP_APP_ID);
@@ -182,6 +219,7 @@ async function boot(): Promise {
runtime = new DesktopRuntime({
repoRoot,
+ resourcesPath: process.resourcesPath,
paths,
serverPort,
webPort,
@@ -198,6 +236,21 @@ async function boot(): Promise {
mainWindow?.webContents.send(DESKTOP_BRIDGE_EVENTS.notificationAction.channel, request);
}
);
+ updateService = new DesktopUpdateService({
+ adapter: new ElectronAutoUpdaterAdapter(autoUpdater),
+ packaged,
+ currentVersion: app.getVersion(),
+ channel: resolveDesktopUpdateChannel(
+ process.env.VERITAS_UPDATE_CHANNEL,
+ app.getVersion(),
+ packaged
+ ),
+ forceDevUpdateConfig: process.env.VERITAS_DESKTOP_UPDATER_FORCE_DEV === 'true',
+ emitStatus: (status) => {
+ mainWindow?.webContents.send(DESKTOP_BRIDGE_EVENTS.updateStatus.channel, status);
+ refreshDesktopMenu();
+ },
+ });
commandDispatcher = new DesktopCommandDispatcher({
runtime,
@@ -206,9 +259,11 @@ async function boot(): Promise {
sendRendererCommand: (command) => {
mainWindow?.webContents.send(DESKTOP_BRIDGE_EVENTS.menuCommand.channel, command);
},
- sendUpdateStatus: (status) => {
- mainWindow?.webContents.send(DESKTOP_BRIDGE_EVENTS.updateStatus.channel, status);
- },
+ checkForUpdates: () =>
+ updateService?.checkForUpdates() ?? Promise.resolve(updateServiceFallback(packaged)),
+ downloadUpdate: () =>
+ updateService?.downloadUpdate() ?? Promise.resolve(updateServiceFallback(packaged)),
+ installUpdate: () => updateService?.installUpdate() ?? updateServiceFallback(packaged),
showTestNotification: () => {
notifications.show({
id: `setup-test-${Date.now()}`,
@@ -224,25 +279,11 @@ async function boot(): Promise {
},
});
- registerDesktopBridge(ipcMain, runtime, shell, packaged, commandDispatcher);
- configureDesktopMenu({
- status: runtime.snapshot(),
- dispatch: (command) => {
- if (commandDispatcher) {
- dispatchDesktopMenuCommand(commandDispatcher, command);
- }
- },
- });
+ registerDesktopBridge(ipcMain, runtime, shell, packaged, commandDispatcher, updateService);
+ refreshDesktopMenu();
runtime.on('status', (status) => {
mainWindow?.webContents.send(DESKTOP_BRIDGE_EVENTS.serverStatus.channel, status);
- configureDesktopMenu({
- status,
- dispatch: (command) => {
- if (commandDispatcher) {
- dispatchDesktopMenuCommand(commandDispatcher, command);
- }
- },
- });
+ refreshDesktopMenu();
});
try {
diff --git a/desktop/src/main/lifecycle.ts b/desktop/src/main/lifecycle.ts
index 724a2239..b31dcaa7 100644
--- a/desktop/src/main/lifecycle.ts
+++ b/desktop/src/main/lifecycle.ts
@@ -4,6 +4,7 @@ import type { DesktopPaths, DesktopRuntimeSecrets, ManagedProcessConfig } from '
export interface DesktopLifecycleOptions {
repoRoot: string;
+ resourcesPath?: string;
paths: DesktopPaths;
serverPort: number;
webPort: number;
@@ -22,13 +23,16 @@ export function buildServerEnvironment(options: DesktopLifecycleOptions): NodeJS
return {
...process.env,
NODE_ENV: options.isPackaged ? 'production' : 'development',
+ ...(options.isPackaged ? { ELECTRON_RUN_AS_NODE: '1' } : {}),
HOST: '127.0.0.1',
PORT: String(options.serverPort),
VERITAS_ADMIN_KEY: options.secrets.adminKey,
VERITAS_JWT_SECRET: options.secrets.jwtSecret,
VERITAS_AUTH_ENABLED: options.isPackaged ? 'true' : 'false',
VERITAS_AUTH_LOCALHOST_BYPASS: 'false',
+ VERITAS_DESKTOP_RUNTIME: options.isPackaged ? '1' : '0',
VERITAS_STORAGE: 'sqlite',
+ DATA_DIR: options.paths.dataDir,
VERITAS_DATA_DIR: options.paths.dataDir,
VERITAS_DISABLE_WATCHERS: '1',
CORS_ORIGINS: `${serverOrigin},${webOrigin},http://localhost:${options.webPort}`,
@@ -49,15 +53,18 @@ export function buildWebEnvironment(options: DesktopLifecycleOptions): NodeJS.Pr
export function createManagedProcessConfigs(
options: DesktopLifecycleOptions
): ManagedProcessConfig[] {
+ const packagedServerRoot = options.resourcesPath
+ ? path.join(options.resourcesPath, 'server')
+ : path.join(options.repoRoot, 'server');
+ const packagedServerEntry =
+ process.env.VERITAS_DESKTOP_SERVER_ENTRY || path.join(packagedServerRoot, 'dist', 'index.js');
+
const serverConfig: ManagedProcessConfig = options.isPackaged
? {
name: 'server',
command: process.execPath,
- args: [
- process.env.VERITAS_DESKTOP_SERVER_ENTRY ||
- path.join(options.repoRoot, 'server/dist/index.js'),
- ],
- cwd: options.repoRoot,
+ args: [packagedServerEntry],
+ cwd: packagedServerRoot,
env: buildServerEnvironment(options),
logFile: path.join(options.paths.logsDir, 'server.log'),
readyUrl: `http://127.0.0.1:${options.serverPort}/api/health`,
diff --git a/desktop/src/main/menu.ts b/desktop/src/main/menu.ts
index 8e8fd1ca..d0ce82cb 100644
--- a/desktop/src/main/menu.ts
+++ b/desktop/src/main/menu.ts
@@ -6,11 +6,15 @@ import {
type DesktopCommandDispatcher,
} from './commands.js';
import type { DesktopStatusSnapshot } from './types.js';
-import type { DesktopCommandName } from '../shared/desktop-bridge-contracts.js';
+import type {
+ DesktopCommandName,
+ DesktopUpdateStatus,
+} from '../shared/desktop-bridge-contracts.js';
export interface ConfigureDesktopMenuOptions {
dispatch(command: DesktopCommandName): void;
status: DesktopStatusSnapshot;
+ updateStatus?: DesktopUpdateStatus;
}
export function configureDesktopMenu(options: ConfigureDesktopMenuOptions): void {
@@ -25,7 +29,7 @@ export function createDesktopMenuTemplate(
return {
label: definition.label,
accelerator: definition.accelerator,
- enabled: isCommandEnabled(name, options.status),
+ enabled: isCommandEnabled(name, options.status, options.updateStatus),
click: () => options.dispatch(name),
};
};
@@ -38,6 +42,8 @@ export function createDesktopMenuTemplate(
command('communication-health'),
{ type: 'separator' },
command('check-for-updates'),
+ command('download-update'),
+ command('install-update'),
{ type: 'separator' },
command('quit'),
],
@@ -78,7 +84,11 @@ export function dispatchDesktopMenuCommand(
void dispatcher.dispatch(createDesktopCommandRequest(command, 'menu'));
}
-function isCommandEnabled(command: DesktopCommandName, status: DesktopStatusSnapshot): boolean {
+function isCommandEnabled(
+ command: DesktopCommandName,
+ status: DesktopStatusSnapshot,
+ updateStatus?: DesktopUpdateStatus
+): boolean {
if (command === 'restart-local-server') {
return status.mode === 'local-dev' || status.mode === 'local-production';
}
@@ -88,5 +98,11 @@ function isCommandEnabled(command: DesktopCommandName, status: DesktopStatusSnap
if (command === 'test-squad-webhook') {
return status.server.state === 'ready';
}
+ if (command === 'download-update') {
+ return updateStatus?.state === 'available';
+ }
+ if (command === 'install-update') {
+ return updateStatus?.state === 'ready';
+ }
return true;
}
diff --git a/desktop/src/main/process-supervisor.ts b/desktop/src/main/process-supervisor.ts
index 2a995c3b..302a27eb 100644
--- a/desktop/src/main/process-supervisor.ts
+++ b/desktop/src/main/process-supervisor.ts
@@ -62,13 +62,17 @@ export class ProcessSupervisor extends EventEmitter {
child.once('exit', (code, signal) => {
this.exitedAt = new Date().toISOString();
this.log(`[desktop] exited code=${code ?? 'null'} signal=${signal ?? 'null'}\n`);
+ const wasReady = this.state === 'ready';
this.child = null;
this.closeLogStream();
- if (this.stopping || code === 0) {
+ if (this.stopping || (code === 0 && wasReady)) {
this.setState('stopped');
return;
}
- this.lastError = `${this.config.name} exited unexpectedly with code ${code ?? 'null'} signal ${signal ?? 'null'}`;
+ const exitDetail = `code ${code ?? 'null'} signal ${signal ?? 'null'}`;
+ this.lastError = wasReady
+ ? `${this.config.name} exited unexpectedly with ${exitDetail}`
+ : `${this.config.name} exited before becoming ready with ${exitDetail}`;
this.setState('failed');
});
}
diff --git a/desktop/src/main/runtime.ts b/desktop/src/main/runtime.ts
index be9e6a29..7c289124 100644
--- a/desktop/src/main/runtime.ts
+++ b/desktop/src/main/runtime.ts
@@ -9,6 +9,7 @@ import type { DesktopPaths, DesktopRuntimeSecrets, DesktopStatusSnapshot } from
export interface DesktopRuntimeOptions {
repoRoot: string;
+ resourcesPath?: string;
paths: DesktopPaths;
serverPort: number;
webPort: number;
@@ -75,12 +76,12 @@ export class DesktopRuntime extends EventEmitter {
await this.ensureDirectories();
await this.writeRuntimeState();
await this.server.start();
- await this.waitForReady(this.serverOrigin + '/api/health', 'server');
+ await this.waitForReady(this.serverOrigin + '/api/health', 'server', this.server);
this.server.markReady();
if (this.web) {
await this.web.start();
- await this.waitForReady(this.rendererOrigin, 'web');
+ await this.waitForReady(this.rendererOrigin, 'web', this.web);
this.web.markReady();
}
@@ -89,7 +90,7 @@ export class DesktopRuntime extends EventEmitter {
async restartLocalServer(): Promise {
await this.server.restart();
- await this.waitForReady(this.serverOrigin + '/api/health', 'server');
+ await this.waitForReady(this.serverOrigin + '/api/health', 'server', this.server);
this.server.markReady();
this.emitStatus();
return this.snapshot();
@@ -126,11 +127,23 @@ export class DesktopRuntime extends EventEmitter {
);
}
- private async waitForReady(url: string, label: string): Promise {
+ private async waitForReady(
+ url: string,
+ label: string,
+ supervisor: ProcessSupervisor
+ ): Promise {
const deadline = Date.now() + 45_000;
let lastError: string | null = null;
while (Date.now() < deadline) {
+ const processSnapshot = supervisor.snapshot();
+ if (processSnapshot.state === 'failed' || processSnapshot.state === 'stopped') {
+ const detail = processSnapshot.lastError ? `: ${processSnapshot.lastError}` : '';
+ this.lastError = `${label} stopped before becoming ready${detail}`;
+ this.emitStatus();
+ throw new Error(this.lastError);
+ }
+
try {
const response = await fetch(url);
if (response.ok) {
diff --git a/desktop/src/main/updates.ts b/desktop/src/main/updates.ts
new file mode 100644
index 00000000..8312a0d9
--- /dev/null
+++ b/desktop/src/main/updates.ts
@@ -0,0 +1,256 @@
+import type { AppUpdater } from 'electron-updater';
+
+import {
+ redactSensitiveString,
+ type DesktopUpdateStatus,
+} from '../shared/desktop-bridge-contracts.js';
+
+type DesktopUpdateEvent =
+ | 'checking-for-update'
+ | 'update-not-available'
+ | 'update-available'
+ | 'download-progress'
+ | 'update-downloaded'
+ | 'error';
+
+export interface DesktopUpdateAdapterConfigureOptions {
+ allowPrerelease: boolean;
+ autoDownload: boolean;
+ autoInstallOnAppQuit: boolean;
+ channel: DesktopUpdateStatus['channel'];
+ forceDevUpdateConfig: boolean;
+}
+
+export interface DesktopUpdateAdapter {
+ configure(options: DesktopUpdateAdapterConfigureOptions): void;
+ on(event: DesktopUpdateEvent, listener: (...args: unknown[]) => void): void;
+ checkForUpdates(): Promise;
+ downloadUpdate(): Promise;
+ quitAndInstall(): void;
+ isUpdaterActive(): boolean;
+}
+
+export interface DesktopUpdateServiceOptions {
+ adapter: DesktopUpdateAdapter;
+ channel: DesktopUpdateStatus['channel'];
+ currentVersion: string;
+ emitStatus?: (status: DesktopUpdateStatus) => void;
+ forceDevUpdateConfig?: boolean;
+ packaged: boolean;
+ now?: () => Date;
+}
+
+export class ElectronAutoUpdaterAdapter implements DesktopUpdateAdapter {
+ constructor(private readonly updater: AppUpdater) {}
+
+ configure(options: DesktopUpdateAdapterConfigureOptions): void {
+ this.updater.autoDownload = options.autoDownload;
+ this.updater.autoInstallOnAppQuit = options.autoInstallOnAppQuit;
+ this.updater.allowPrerelease = options.allowPrerelease;
+ this.updater.channel = options.channel === 'stable' ? null : options.channel;
+ this.updater.forceDevUpdateConfig = options.forceDevUpdateConfig;
+ }
+
+ on(event: DesktopUpdateEvent, listener: (...args: unknown[]) => void): void {
+ this.updater.on(event, listener as never);
+ }
+
+ checkForUpdates(): Promise {
+ return this.updater.checkForUpdates();
+ }
+
+ downloadUpdate(): Promise {
+ return this.updater.downloadUpdate();
+ }
+
+ quitAndInstall(): void {
+ this.updater.quitAndInstall();
+ }
+
+ isUpdaterActive(): boolean {
+ return this.updater.isUpdaterActive();
+ }
+}
+
+export class DesktopUpdateService {
+ private readonly enabled: boolean;
+ private status: DesktopUpdateStatus;
+
+ constructor(private readonly options: DesktopUpdateServiceOptions) {
+ this.enabled = options.packaged || options.forceDevUpdateConfig === true;
+ this.status = this.enabled
+ ? this.createStatus('idle', 'Update checks are ready.')
+ : this.createStatus('unsupported', 'Updater checks run only from packaged builds.');
+
+ options.adapter.configure({
+ allowPrerelease: options.channel !== 'stable',
+ autoDownload: false,
+ autoInstallOnAppQuit: false,
+ channel: options.channel,
+ forceDevUpdateConfig: options.forceDevUpdateConfig === true,
+ });
+ this.bindEvents();
+ }
+
+ snapshot(): DesktopUpdateStatus {
+ return this.status;
+ }
+
+ async checkForUpdates(): Promise {
+ if (!this.canRunUpdater()) {
+ return this.status;
+ }
+
+ this.setStatus('checking', 'Checking for updates.');
+ try {
+ await this.options.adapter.checkForUpdates();
+ if (this.status.state === 'checking') {
+ this.setStatus('idle', 'No update metadata was returned.');
+ }
+ } catch (error) {
+ this.setStatus('failed', redactUpdateError(error));
+ }
+ return this.status;
+ }
+
+ async downloadUpdate(): Promise {
+ if (!this.canRunUpdater()) {
+ return this.status;
+ }
+
+ this.setStatus('downloading', 'Downloading update.');
+ try {
+ await this.options.adapter.downloadUpdate();
+ if (this.status.state === 'downloading') {
+ this.setStatus('ready', 'Update downloaded and ready to install.');
+ }
+ } catch (error) {
+ this.setStatus('failed', redactUpdateError(error));
+ }
+ return this.status;
+ }
+
+ installUpdate(): DesktopUpdateStatus {
+ if (!this.canRunUpdater()) {
+ return this.status;
+ }
+
+ if (this.status.state !== 'ready') {
+ this.setStatus('failed', 'No downloaded update is ready to install.');
+ return this.status;
+ }
+
+ this.options.adapter.quitAndInstall();
+ this.setStatus('ready', 'Installing update.');
+ return this.status;
+ }
+
+ private bindEvents(): void {
+ this.options.adapter.on('checking-for-update', () => {
+ this.setStatus('checking', 'Checking for updates.');
+ });
+ this.options.adapter.on('update-not-available', () => {
+ this.setStatus('idle', 'Already running the latest version.');
+ });
+ this.options.adapter.on('update-available', (info) => {
+ this.setStatus(
+ 'available',
+ `Update ${readUpdateVersion(info) ?? 'available'} is available.`,
+ readUpdateVersion(info)
+ );
+ });
+ this.options.adapter.on('download-progress', (info) => {
+ const percent = readDownloadPercent(info);
+ this.setStatus(
+ 'downloading',
+ percent === null ? 'Downloading update.' : `${percent}% downloaded.`
+ );
+ });
+ this.options.adapter.on('update-downloaded', (info) => {
+ this.setStatus(
+ 'ready',
+ `Update ${readUpdateVersion(info) ?? 'downloaded'} is ready to install.`,
+ readUpdateVersion(info)
+ );
+ });
+ this.options.adapter.on('error', (error) => {
+ this.setStatus('failed', redactUpdateError(error));
+ });
+ }
+
+ private canRunUpdater(): boolean {
+ if (!this.enabled) {
+ this.setStatus('unsupported', 'Updater checks run only from packaged builds.');
+ return false;
+ }
+
+ if (!this.options.adapter.isUpdaterActive()) {
+ this.setStatus('unsupported', 'Updater provider is not active for this build.');
+ return false;
+ }
+
+ return true;
+ }
+
+ private setStatus(
+ state: DesktopUpdateStatus['state'],
+ detail: string,
+ availableVersion?: string
+ ): void {
+ this.status = this.createStatus(state, detail, availableVersion);
+ this.options.emitStatus?.(this.status);
+ }
+
+ private createStatus(
+ state: DesktopUpdateStatus['state'],
+ detail: string,
+ availableVersion?: string
+ ): DesktopUpdateStatus {
+ return {
+ state,
+ currentVersion: this.options.currentVersion,
+ availableVersion,
+ channel: this.options.channel,
+ checkedAt: (this.options.now ?? (() => new Date()))().toISOString(),
+ detail,
+ };
+ }
+}
+
+export function resolveDesktopUpdateChannel(
+ requested: string | undefined,
+ currentVersion: string,
+ packaged: boolean
+): DesktopUpdateStatus['channel'] {
+ const normalized = requested?.trim().toLowerCase();
+ if (normalized === 'stable' || normalized === 'beta' || normalized === 'dev') {
+ return normalized;
+ }
+ if (/\b(alpha|beta|rc|next|canary|dev)\b/i.test(currentVersion)) {
+ return 'beta';
+ }
+ return packaged ? 'stable' : 'dev';
+}
+
+function readUpdateVersion(value: unknown): string | undefined {
+ if (!isRecord(value)) {
+ return undefined;
+ }
+ const version = value.version;
+ return typeof version === 'string' && version.trim() ? version.trim() : undefined;
+}
+
+function readDownloadPercent(value: unknown): number | null {
+ if (!isRecord(value) || typeof value.percent !== 'number' || !Number.isFinite(value.percent)) {
+ return null;
+ }
+ return Math.max(0, Math.min(100, Math.round(value.percent)));
+}
+
+function redactUpdateError(error: unknown): string {
+ return redactSensitiveString(error instanceof Error ? error.message : String(error));
+}
+
+function isRecord(value: unknown): value is Record {
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
diff --git a/desktop/src/shared/desktop-bridge-contracts.ts b/desktop/src/shared/desktop-bridge-contracts.ts
index 63c096df..0583f40f 100644
--- a/desktop/src/shared/desktop-bridge-contracts.ts
+++ b/desktop/src/shared/desktop-bridge-contracts.ts
@@ -95,6 +95,8 @@ export const DESKTOP_COMMAND_NAMES = [
'show-diagnostics',
'create-debug-bundle',
'check-for-updates',
+ 'download-update',
+ 'install-update',
'test-notification',
'test-squad-webhook',
'copy-redacted-diagnostics',
@@ -161,6 +163,7 @@ export interface DesktopDiagnosticsBundleResult {
export interface DesktopUpdateStatus {
state: 'unsupported' | 'idle' | 'checking' | 'available' | 'downloading' | 'ready' | 'failed';
currentVersion: string;
+ availableVersion?: string;
channel: 'dev' | 'beta' | 'stable';
checkedAt: string;
detail?: string;
diff --git a/docs/DESKTOP-RELEASE.md b/docs/DESKTOP-RELEASE.md
new file mode 100644
index 00000000..db7a5386
--- /dev/null
+++ b/docs/DESKTOP-RELEASE.md
@@ -0,0 +1,117 @@
+# Veritas Kanban Desktop Release
+
+This guide covers the v5 macOS desktop packaging path: unsigned PR artifacts,
+signed/notarized release artifacts, update metadata, and smoke testing.
+
+## Local Commands
+
+Run these from the repository root:
+
+```bash
+pnpm desktop:package:mac:dir
+pnpm desktop:package:mac:unsigned
+pnpm desktop:release:mac
+```
+
+`desktop:package:mac:dir` creates an unpacked local app for fast inspection.
+`desktop:package:mac:unsigned` creates unsigned DMG/ZIP artifacts and update
+metadata for PR validation. `desktop:release:mac` expects signing and
+notarization credentials and publishes update metadata through electron-builder.
+
+The package step builds the workspace, stages the production server runtime in
+`desktop/.desktop-release/server`, stages the built web app in
+`desktop/.desktop-release/web`, and writes artifacts to `desktop/release/`.
+Both staging and release directories are ignored by git.
+
+## GitHub Workflows
+
+`Desktop Artifacts` runs on desktop/server/web/shared changes and on manual
+dispatch. It builds unsigned macOS artifacts on `macos-15`, uploads the DMG,
+ZIP, blockmap, and update YAML files, and does not require Apple credentials.
+
+`Desktop Release` runs on manual dispatch or a published GitHub release. It
+requires the signing secrets below, builds signed/notarized macOS artifacts,
+and publishes update metadata with the GitHub provider.
+
+## Required Release Secrets
+
+Configure these repository secrets before running `Desktop Release`:
+
+- `MACOS_CSC_LINK`: base64 encoded `.p12` Developer ID Application certificate
+ or a secure URL accepted by electron-builder `CSC_LINK`.
+- `MACOS_CSC_KEY_PASSWORD`: password for the `.p12` signing identity.
+- `APPLE_ID`: Apple Developer account email for notarization.
+- `APPLE_APP_SPECIFIC_PASSWORD`: app-specific password for notarization.
+- `APPLE_TEAM_ID`: Apple Developer team ID.
+
+The workflow maps those secrets to electron-builder's `CSC_LINK`,
+`CSC_KEY_PASSWORD`, `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, and
+`APPLE_TEAM_ID` environment variables.
+
+## Update Channels
+
+The app uses `electron-updater` with manual download/install behavior:
+
+- `stable`: default packaged release channel.
+- `beta`: prerelease/test channel.
+- `dev`: explicit development channel for controlled test metadata.
+
+Set `VERITAS_UPDATE_CHANNEL=stable|beta|dev` in release workflows or local
+packaged test runs. Dev-mode update checks remain unsupported unless
+`VERITAS_DESKTOP_UPDATER_FORCE_DEV=true` is set with a valid dev update config.
+
+The native desktop bridge exposes update status events for checking,
+available, downloading, ready, failed, and unsupported states. The menu enables
+download only when an update is available and install only when an update has
+downloaded.
+
+## Release Checklist
+
+- Bump all workspace package versions together.
+- Update `CHANGELOG.md`.
+- Run `pnpm typecheck`, `pnpm lint:budget`, `pnpm build`, and
+ `pnpm test:unit`.
+- Run `pnpm desktop:package:mac:unsigned` and inspect artifact names.
+- Run `Desktop Artifacts` and download the uploaded DMG/ZIP/update metadata.
+- Run `Desktop Release` only after Apple signing secrets are configured.
+- Confirm notarization succeeds and the DMG installs without Gatekeeper
+ warnings on a clean Mac.
+- Confirm a first run creates the profile/workspace app data directories.
+- Confirm update check, download, install, failed-download, and rollback paths
+ on the selected channel.
+
+## Smoke Tests
+
+Unsigned PR artifact:
+
+1. Download `veritas-kanban-mac-unsigned` from the workflow run.
+2. Mount the DMG and drag Veritas Kanban into `/Applications`.
+3. For unsigned local artifacts only, use right-click Open or remove quarantine
+ with `xattr -dr com.apple.quarantine "/Applications/Veritas Kanban.app"`.
+4. Launch the app and confirm the desktop status page reaches the local app.
+5. Confirm app data appears under
+ `~/Library/Application Support/@veritas-kanban/desktop/profiles/default/workspaces/local/`.
+
+Signed release artifact:
+
+1. Install the DMG on a clean Mac.
+2. Launch normally. There should be no Gatekeeper warning.
+3. Confirm local server health through the desktop UI and logs.
+4. Check for updates from the native menu.
+5. Publish a higher test-channel build, then confirm available, downloading,
+ ready, and install states.
+
+Rollback:
+
+1. Quit Veritas Kanban.
+2. Install the previous signed DMG.
+3. Launch and confirm the existing profile/workspace data is preserved.
+4. If an update artifact is bad, remove or supersede the affected GitHub
+ release assets and publish corrected update metadata.
+
+## Future Targets
+
+Linux and Windows packages are intentionally not v5 Mac GA blockers. The
+current packaging config keeps artifact naming and update-channel conventions
+portable, but Windows signing, Linux package formats, auto-launch behavior, and
+OS-specific smoke tests should be handled in follow-up issues.
diff --git a/package.json b/package.json
index dcdd09db..c62ebc5a 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,9 @@
"desktop:dev": "pnpm --filter @veritas-kanban/desktop dev",
"desktop:dev:fresh": "pnpm --filter @veritas-kanban/desktop dev:fresh",
"desktop:build": "pnpm --filter @veritas-kanban/desktop build",
+ "desktop:package:mac:dir": "pnpm build && pnpm --filter @veritas-kanban/desktop package:mac:dir",
+ "desktop:package:mac:unsigned": "pnpm build && pnpm --filter @veritas-kanban/desktop package:mac:unsigned",
+ "desktop:release:mac": "pnpm build && pnpm --filter @veritas-kanban/desktop release:mac",
"desktop:test": "pnpm --filter @veritas-kanban/desktop test",
"dev:clean": "bash scripts/dev-clean.sh",
"dev:watchdog": "bash scripts/dev-watchdog.sh",
@@ -65,19 +68,6 @@
"prettier --write"
]
},
- "pnpm": {
- "overrides": {
- "@xmldom/xmldom": ">=0.8.13",
- "fast-uri": ">=3.1.2",
- "hono": ">=4.12.18",
- "ip-address": ">=10.1.1",
- "postcss": ">=8.5.10",
- "qs": "^6.14.2",
- "minimatch": ">=10.2.3",
- "path-to-regexp": ">=8.4.0",
- "tmp": ">=0.2.6"
- }
- },
"repository": {
"type": "git",
"url": "https://github.com/BradGroux/veritas-kanban.git"
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 76fd53cc..f4cd8e27 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4,17 +4,6 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
-overrides:
- '@xmldom/xmldom': '>=0.8.13'
- fast-uri: '>=3.1.2'
- hono: '>=4.12.18'
- ip-address: '>=10.1.1'
- postcss: '>=8.5.10'
- qs: ^6.14.2
- minimatch: '>=10.2.3'
- path-to-regexp: '>=8.4.0'
- tmp: '>=0.2.6'
-
importers:
.:
devDependencies:
@@ -85,6 +74,9 @@ importers:
'@veritas-kanban/shared':
specifier: workspace:*
version: link:../shared
+ electron-updater:
+ specifier: ^6.8.3
+ version: 6.8.3
devDependencies:
'@types/node':
specifier: ^25.7.0
@@ -92,6 +84,9 @@ importers:
electron:
specifier: ^39.2.6
version: 39.8.10
+ electron-builder:
+ specifier: ^26.8.1
+ version: 26.8.1(electron-builder-squirrel-windows@26.8.1)
electron-vite:
specifier: ^5.0.0
version: 5.0.0(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0))
@@ -114,7 +109,7 @@ importers:
specifier: workspace:*
version: link:../shared
hono:
- specifier: '>=4.12.18'
+ specifier: ^4.12.18
version: 4.12.18
zod:
specifier: ^4.4.3
@@ -417,7 +412,7 @@ importers:
specifier: ^29.1.1
version: 29.1.1(@noble/hashes@1.8.0)
postcss:
- specifier: '>=8.5.10'
+ specifier: ^8.5.14
version: 8.5.14
postcss-preset-mantine:
specifier: ^1.18.0
@@ -439,6 +434,12 @@ importers:
version: 4.1.6(@types/node@25.7.0)(@vitest/coverage-v8@4.1.6)(jsdom@29.1.1(@noble/hashes@1.8.0))(msw@2.14.3(@types/node@25.7.0)(typescript@6.0.3))(vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.7)(jiti@2.7.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0))
packages:
+ 7zip-bin@5.2.0:
+ resolution:
+ {
+ integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==,
+ }
+
'@adobe/css-tools@4.4.4':
resolution:
{
@@ -827,6 +828,13 @@ packages:
}
engines: { node: '>=20.19.0' }
+ '@develar/schema-utils@2.6.5':
+ resolution:
+ {
+ integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==,
+ }
+ engines: { node: '>= 8.9.0' }
+
'@dnd-kit/accessibility@3.1.1':
resolution:
{
@@ -877,6 +885,21 @@ packages:
peerDependencies:
'@noble/ciphers': ^1.0.0
+ '@electron/asar@3.4.1':
+ resolution:
+ {
+ integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==,
+ }
+ engines: { node: '>=10.12.0' }
+ hasBin: true
+
+ '@electron/fuses@1.8.0':
+ resolution:
+ {
+ integrity: sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==,
+ }
+ hasBin: true
+
'@electron/get@2.0.3':
resolution:
{
@@ -884,6 +907,51 @@ packages:
}
engines: { node: '>=12' }
+ '@electron/get@3.1.0':
+ resolution:
+ {
+ integrity: sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==,
+ }
+ engines: { node: '>=14' }
+
+ '@electron/notarize@2.5.0':
+ resolution:
+ {
+ integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==,
+ }
+ engines: { node: '>= 10.0.0' }
+
+ '@electron/osx-sign@1.3.3':
+ resolution:
+ {
+ integrity: sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==,
+ }
+ engines: { node: '>=12.0.0' }
+ hasBin: true
+
+ '@electron/rebuild@4.0.4':
+ resolution:
+ {
+ integrity: sha512-Rzc39XPdk/+/wBG8MfwAHohXflep0ITUfulb6Rgz3R0NeSB1noE+E9/M/cb8ftCAiyDD9PPhLuuWgE1GaInbKg==,
+ }
+ engines: { node: '>=22.12.0' }
+ hasBin: true
+
+ '@electron/universal@2.0.3':
+ resolution:
+ {
+ integrity: sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==,
+ }
+ engines: { node: '>=16.4' }
+
+ '@electron/windows-sign@1.2.2':
+ resolution:
+ {
+ integrity: sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==,
+ }
+ engines: { node: '>=14.14' }
+ hasBin: true
+
'@emnapi/core@1.10.0':
resolution:
{
@@ -1509,7 +1577,7 @@ packages:
}
engines: { node: '>=18.14.1' }
peerDependencies:
- hono: '>=4.12.18'
+ hono: ^4
'@humanfs/core@0.19.1':
resolution:
@@ -1589,6 +1657,13 @@ packages:
'@types/node':
optional: true
+ '@isaacs/fs-minipass@4.0.1':
+ resolution:
+ {
+ integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==,
+ }
+ engines: { node: '>=18.0.0' }
+
'@jridgewell/gen-mapping@0.3.13':
resolution:
{
@@ -1638,6 +1713,20 @@ packages:
integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==,
}
+ '@malept/cross-spawn-promise@2.0.0':
+ resolution:
+ {
+ integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==,
+ }
+ engines: { node: '>= 12.13.0' }
+
+ '@malept/flatpak-bundler@0.4.0':
+ resolution:
+ {
+ integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==,
+ }
+ engines: { node: '>= 10.0.0' }
+
'@mantine/core@9.2.2':
resolution:
{
@@ -2736,6 +2825,12 @@ packages:
integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==,
}
+ '@types/fs-extra@9.0.13':
+ resolution:
+ {
+ integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==,
+ }
+
'@types/hast@3.0.4':
resolution:
{
@@ -2820,6 +2915,12 @@ packages:
integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==,
}
+ '@types/plist@3.0.5':
+ resolution:
+ {
+ integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==,
+ }
+
'@types/qs@6.15.0':
resolution:
{
@@ -2943,6 +3044,12 @@ packages:
integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==,
}
+ '@types/verror@1.10.11':
+ resolution:
+ {
+ integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==,
+ }
+
'@types/ws@8.18.1':
resolution:
{
@@ -3122,6 +3229,13 @@ packages:
integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==,
}
+ '@xmldom/xmldom@0.8.13':
+ resolution:
+ {
+ integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==,
+ }
+ engines: { node: '>=10.0.0' }
+
'@xmldom/xmldom@0.9.10':
resolution:
{
@@ -3129,6 +3243,13 @@ packages:
}
engines: { node: '>=14.6' }
+ abbrev@4.0.0:
+ resolution:
+ {
+ integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==,
+ }
+ engines: { node: ^20.17.0 || >=22.9.0 }
+
accepts@2.0.0:
resolution:
{
@@ -3170,6 +3291,14 @@ packages:
ajv:
optional: true
+ ajv-keywords@3.5.2:
+ resolution:
+ {
+ integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==,
+ }
+ peerDependencies:
+ ajv: ^6.9.1
+
ajv@6.15.0:
resolution:
{
@@ -3230,6 +3359,22 @@ packages:
}
engines: { node: '>=12' }
+ app-builder-bin@5.0.0-alpha.12:
+ resolution:
+ {
+ integrity: sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==,
+ }
+
+ app-builder-lib@26.8.1:
+ resolution:
+ {
+ integrity: sha512-p0Im/Dx5C4tmz8QEE1Yn4MkuPC8PrnlRneMhWJj7BBXQfNTJUshM/bp3lusdEsDbvvfJZpXWnYesgSLvwtM2Zw==,
+ }
+ engines: { node: '>=14.0.0' }
+ peerDependencies:
+ dmg-builder: 26.8.1
+ electron-builder-squirrel-windows: 26.8.1
+
append-field@1.0.0:
resolution:
{
@@ -3337,6 +3482,13 @@ packages:
integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==,
}
+ assert-plus@1.0.0:
+ resolution:
+ {
+ integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==,
+ }
+ engines: { node: '>=0.8' }
+
assertion-error@2.0.1:
resolution:
{
@@ -3357,6 +3509,20 @@ packages:
integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==,
}
+ astral-regex@2.0.0:
+ resolution:
+ {
+ integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==,
+ }
+ engines: { node: '>=8' }
+
+ async-exit-hook@2.0.1:
+ resolution:
+ {
+ integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==,
+ }
+ engines: { node: '>=0.12.0' }
+
async-function@1.0.0:
resolution:
{
@@ -3376,6 +3542,13 @@ packages:
integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==,
}
+ at-least-node@1.0.0:
+ resolution:
+ {
+ integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==,
+ }
+ engines: { node: '>= 4.0.0' }
+
atomic-sleep@1.0.0:
resolution:
{
@@ -3391,7 +3564,7 @@ packages:
engines: { node: ^10 || ^12 || >=14 }
hasBin: true
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: ^8.1.0
available-typed-arrays@1.0.7:
resolution:
@@ -3406,6 +3579,12 @@ packages:
integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==,
}
+ balanced-match@1.0.2:
+ resolution:
+ {
+ integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==,
+ }
+
balanced-match@4.0.4:
resolution:
{
@@ -3479,6 +3658,18 @@ packages:
}
deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
+ brace-expansion@1.1.15:
+ resolution:
+ {
+ integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==,
+ }
+
+ brace-expansion@2.1.1:
+ resolution:
+ {
+ integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==,
+ }
+
brace-expansion@5.0.5:
resolution:
{
@@ -3539,6 +3730,19 @@ packages:
}
engines: { node: '>=0.2.0' }
+ builder-util-runtime@9.5.1:
+ resolution:
+ {
+ integrity: sha512-qt41tMfgHTllhResqM5DcnHyDIWNgzHvuY2jDcYP9iaGpkWxTUzV6GQjDeLnlR1/DtdlcsWQbA7sByMpmJFTLQ==,
+ }
+ engines: { node: '>=12.0.0' }
+
+ builder-util@26.8.1:
+ resolution:
+ {
+ integrity: sha512-pm1lTYbGyc90DHgCDO7eo8Rl4EqKLciayNbZqGziqnH9jrlKe8ZANGdityLZU+pJh16dfzjAx2xQq9McuIPEtw==,
+ }
+
bundle-name@4.1.0:
resolution:
{
@@ -3691,6 +3895,33 @@ packages:
integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==,
}
+ chownr@3.0.0:
+ resolution:
+ {
+ integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==,
+ }
+ engines: { node: '>=18' }
+
+ chromium-pickle-js@0.2.0:
+ resolution:
+ {
+ integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==,
+ }
+
+ ci-info@4.3.1:
+ resolution:
+ {
+ integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==,
+ }
+ engines: { node: '>=8' }
+
+ ci-info@4.4.0:
+ resolution:
+ {
+ integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==,
+ }
+ engines: { node: '>=8' }
+
class-variance-authority@0.7.1:
resolution:
{
@@ -3711,6 +3942,13 @@ packages:
}
engines: { node: '>=6' }
+ cli-truncate@2.1.0:
+ resolution:
+ {
+ integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==,
+ }
+ engines: { node: '>=8' }
+
cli-truncate@5.2.0:
resolution:
{
@@ -3797,6 +4035,13 @@ packages:
}
engines: { node: '>=20' }
+ commander@5.1.0:
+ resolution:
+ {
+ integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==,
+ }
+ engines: { node: '>= 6' }
+
commander@6.2.0:
resolution:
{
@@ -3811,6 +4056,13 @@ packages:
}
engines: { node: ^12.20.0 || >=14 }
+ compare-version@0.1.2:
+ resolution:
+ {
+ integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==,
+ }
+ engines: { node: '>=0.10.0' }
+
component-emitter@1.3.1:
resolution:
{
@@ -3838,6 +4090,12 @@ packages:
}
engines: { node: '>= 0.8.0' }
+ concat-map@0.0.1:
+ resolution:
+ {
+ integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==,
+ }
+
concat-stream@2.0.0:
resolution:
{
@@ -3920,6 +4178,12 @@ packages:
integrity: sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==,
}
+ core-util-is@1.0.2:
+ resolution:
+ {
+ integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==,
+ }
+
core-util-is@1.0.3:
resolution:
{
@@ -3960,6 +4224,18 @@ packages:
}
engines: { node: '>= 10' }
+ crc@3.8.0:
+ resolution:
+ {
+ integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==,
+ }
+
+ cross-dirname@0.1.0:
+ resolution:
+ {
+ integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==,
+ }
+
cross-spawn@7.0.6:
resolution:
{
@@ -4297,6 +4573,27 @@ packages:
integrity: sha512-98l0sW87ZT58pU4i61wa2OHwxbiYSbuxsCBozaVnYX2iCnr3bLM3fIes1/ej7h1YdOKuKt/MLs706TVnALA65w==,
}
+ dir-compare@4.2.0:
+ resolution:
+ {
+ integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==,
+ }
+
+ dmg-builder@26.8.1:
+ resolution:
+ {
+ integrity: sha512-glMJgnTreo8CFINujtAhCgN96QAqApDMZ8Vl1r8f0QT8QprvC1UCltV4CcWj20YoIyLZx6IUskaJZ0NV8fokcg==,
+ }
+
+ dmg-license@1.0.11:
+ resolution:
+ {
+ integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==,
+ }
+ engines: { node: '>=8' }
+ os: [darwin]
+ hasBin: true
+
doctrine@2.1.0:
resolution:
{
@@ -4360,6 +4657,20 @@ packages:
integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==,
}
+ dotenv-expand@11.0.7:
+ resolution:
+ {
+ integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==,
+ }
+ engines: { node: '>=12' }
+
+ dotenv@16.6.1:
+ resolution:
+ {
+ integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==,
+ }
+ engines: { node: '>=12' }
+
dotenv@17.4.2:
resolution:
{
@@ -4405,12 +4716,46 @@ packages:
integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==,
}
+ ejs@3.1.10:
+ resolution:
+ {
+ integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==,
+ }
+ engines: { node: '>=0.10.0' }
+ hasBin: true
+
+ electron-builder-squirrel-windows@26.8.1:
+ resolution:
+ {
+ integrity: sha512-o288fIdgPLHA76eDrFADHPoo7VyGkDCYbLV1GzndaMSAVBoZrGvM9m2IehdcVMzdAZJ2eV9bgyissQXHv5tGzA==,
+ }
+
+ electron-builder@26.8.1:
+ resolution:
+ {
+ integrity: sha512-uWhx1r74NGpCagG0ULs/P9Nqv2nsoo+7eo4fLUOB8L8MdWltq9odW/uuLXMFCDGnPafknYLZgjNX0ZIFRzOQAw==,
+ }
+ engines: { node: '>=14.0.0' }
+ hasBin: true
+
+ electron-publish@26.8.1:
+ resolution:
+ {
+ integrity: sha512-q+jrSTIh/Cv4eGZa7oVR+grEJo/FoLMYBAnSL5GCtqwUpr1T+VgKB/dn1pnzxIxqD8S/jP1yilT9VrwCqINR4w==,
+ }
+
electron-to-chromium@1.5.351:
resolution:
{
integrity: sha512-9D7Iqx8RImSvCnOsj86rCH6eQjZFQoM04Jn6HnZVM0Nu/G58/gmKYQ1d12MZTbjQbQSTGI8nwEy07ErsA2slLA==,
}
+ electron-updater@6.8.3:
+ resolution:
+ {
+ integrity: sha512-Z6sgw3jgbikWKXei1ENdqFOxBP0WlXg3TtKfz0rgw2vIZFJUyI4pD7ZN7jrkm7EoMK+tcm/qTnPUdqfZukBlBQ==,
+ }
+
electron-vite@5.0.0:
resolution:
{
@@ -4425,6 +4770,13 @@ packages:
'@swc/core':
optional: true
+ electron-winstaller@5.4.0:
+ resolution:
+ {
+ integrity: sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==,
+ }
+ engines: { node: '>=8.0.0' }
+
electron@39.8.10:
resolution:
{
@@ -4500,6 +4852,12 @@ packages:
}
engines: { node: '>=18' }
+ err-code@2.0.3:
+ resolution:
+ {
+ integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==,
+ }
+
error-ex@1.3.4:
resolution:
{
@@ -4792,6 +5150,12 @@ packages:
}
engines: { node: '>=12.0.0' }
+ exponential-backoff@3.1.3:
+ resolution:
+ {
+ integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==,
+ }
+
express-rate-limit@8.5.0:
resolution:
{
@@ -4838,6 +5202,13 @@ packages:
engines: { node: '>= 10.17.0' }
hasBin: true
+ extsprintf@1.4.1:
+ resolution:
+ {
+ integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==,
+ }
+ engines: { '0': node >=0.6.0 }
+
fast-copy@4.0.3:
resolution:
{
@@ -4964,6 +5335,12 @@ packages:
}
engines: { node: '>=22' }
+ filelist@1.0.6:
+ resolution:
+ {
+ integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==,
+ }
+
fill-range@7.1.1:
resolution:
{
@@ -5052,6 +5429,13 @@ packages:
integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==,
}
+ fs-extra@10.1.0:
+ resolution:
+ {
+ integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==,
+ }
+ engines: { node: '>=12' }
+
fs-extra@11.3.4:
resolution:
{
@@ -5059,6 +5443,13 @@ packages:
}
engines: { node: '>=14.14' }
+ fs-extra@7.0.1:
+ resolution:
+ {
+ integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==,
+ }
+ engines: { node: '>=6 <7 || >=8' }
+
fs-extra@8.1.0:
resolution:
{
@@ -5066,6 +5457,13 @@ packages:
}
engines: { node: '>=6 <7 || >=8' }
+ fs-extra@9.1.0:
+ resolution:
+ {
+ integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==,
+ }
+ engines: { node: '>=10' }
+
fs.realpath@1.0.0:
resolution:
{
@@ -5418,6 +5816,13 @@ packages:
}
engines: { node: '>=16.9.0' }
+ hosted-git-info@4.1.0:
+ resolution:
+ {
+ integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==,
+ }
+ engines: { node: '>=10' }
+
html-encoding-sniffer@6.0.0:
resolution:
{
@@ -5456,6 +5861,13 @@ packages:
}
engines: { node: '>= 0.8' }
+ http-proxy-agent@7.0.2:
+ resolution:
+ {
+ integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==,
+ }
+ engines: { node: '>= 14' }
+
http2-wrapper@1.0.3:
resolution:
{
@@ -5492,6 +5904,21 @@ packages:
engines: { node: '>=18' }
hasBin: true
+ iconv-corefoundation@1.1.7:
+ resolution:
+ {
+ integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==,
+ }
+ engines: { node: ^8.11.2 || >=10 }
+ os: [darwin]
+
+ iconv-lite@0.6.3:
+ resolution:
+ {
+ integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==,
+ }
+ engines: { node: '>=0.10.0' }
+
iconv-lite@0.7.2:
resolution:
{
@@ -5591,6 +6018,13 @@ packages:
}
engines: { node: '>=12' }
+ ip-address@10.1.0:
+ resolution:
+ {
+ integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==,
+ }
+ engines: { node: '>= 12' }
+
ip-address@10.2.0:
resolution:
{
@@ -5954,6 +6388,20 @@ packages:
integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==,
}
+ isbinaryfile@4.0.10:
+ resolution:
+ {
+ integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==,
+ }
+ engines: { node: '>= 8.0.0' }
+
+ isbinaryfile@5.0.7:
+ resolution:
+ {
+ integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==,
+ }
+ engines: { node: '>= 18.0.0' }
+
isexe@2.0.0:
resolution:
{
@@ -5967,6 +6415,13 @@ packages:
}
engines: { node: '>=18' }
+ isexe@4.0.0:
+ resolution:
+ {
+ integrity: sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==,
+ }
+ engines: { node: '>=20' }
+
istanbul-lib-coverage@3.2.2:
resolution:
{
@@ -5995,6 +6450,14 @@ packages:
}
engines: { node: '>= 0.4' }
+ jake@10.9.4:
+ resolution:
+ {
+ integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==,
+ }
+ engines: { node: '>=10' }
+ hasBin: true
+
jiti@2.7.0:
resolution:
{
@@ -6195,6 +6658,12 @@ packages:
integrity: sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw==,
}
+ lazy-val@1.0.5:
+ resolution:
+ {
+ integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==,
+ }
+
lazystream@1.0.1:
resolution:
{
@@ -6483,6 +6952,12 @@ packages:
integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==,
}
+ lodash@4.18.1:
+ resolution:
+ {
+ integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==,
+ }
+
log-symbols@6.0.0:
resolution:
{
@@ -6542,6 +7017,13 @@ packages:
integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==,
}
+ lru-cache@6.0.0:
+ resolution:
+ {
+ integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==,
+ }
+ engines: { node: '>=10' }
+
lucide-react@1.14.0:
resolution:
{
@@ -6994,12 +7476,46 @@ packages:
}
engines: { node: 18 || 20 || >=22 }
+ minimatch@3.1.5:
+ resolution:
+ {
+ integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==,
+ }
+
+ minimatch@5.1.9:
+ resolution:
+ {
+ integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==,
+ }
+ engines: { node: '>=10' }
+
+ minimatch@9.0.9:
+ resolution:
+ {
+ integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==,
+ }
+ engines: { node: '>=16 || 14 >=14.17' }
+
minimist@1.2.8:
resolution:
{
integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==,
}
+ minipass@7.1.3:
+ resolution:
+ {
+ integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==,
+ }
+ engines: { node: '>=16 || 14 >=14.17' }
+
+ minizlib@3.1.0:
+ resolution:
+ {
+ integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==,
+ }
+ engines: { node: '>= 18' }
+
mkdirp@0.5.6:
resolution:
{
@@ -7082,6 +7598,19 @@ packages:
}
engines: { node: '>= 0.6' }
+ node-abi@4.31.0:
+ resolution:
+ {
+ integrity: sha512-Erq5w/t3syw3s4sDsUaX4QttIdBPsGKTT1DTRsCkTonGggczhlDKm/wDX3o+HPJpQ41EjXCbcmXf0tgr5YZJXw==,
+ }
+ engines: { node: '>=22.12.0' }
+
+ node-addon-api@1.7.2:
+ resolution:
+ {
+ integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==,
+ }
+
node-addon-api@8.7.0:
resolution:
{
@@ -7089,6 +7618,12 @@ packages:
}
engines: { node: ^18 || ^20 || >= 21 }
+ node-api-version@0.2.1:
+ resolution:
+ {
+ integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==,
+ }
+
node-domexception@1.0.0:
resolution:
{
@@ -7118,12 +7653,28 @@ packages:
}
hasBin: true
+ node-gyp@12.3.0:
+ resolution:
+ {
+ integrity: sha512-QNcUWM+HgJplcPzBvFBZ9VXacyGZ4+VTOb80PwWR+TlVzoHbRKULNEzpRsnaoxG3Wzr7Qh7BYxGDU3CbKib2Yg==,
+ }
+ engines: { node: ^20.17.0 || >=22.9.0 }
+ hasBin: true
+
node-releases@2.0.38:
resolution:
{
integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==,
}
+ nopt@9.0.0:
+ resolution:
+ {
+ integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==,
+ }
+ engines: { node: ^20.17.0 || >=22.9.0 }
+ hasBin: true
+
normalize-path@3.0.0:
resolution:
{
@@ -7414,6 +7965,12 @@ packages:
integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==,
}
+ path-to-regexp@6.3.0:
+ resolution:
+ {
+ integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==,
+ }
+
path-to-regexp@8.4.2:
resolution:
{
@@ -7426,6 +7983,13 @@ packages:
integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==,
}
+ pe-library@0.4.1:
+ resolution:
+ {
+ integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==,
+ }
+ engines: { node: '>=12', npm: '>=6' }
+
pend@1.2.0:
resolution:
{
@@ -7501,6 +8065,20 @@ packages:
engines: { node: '>=18' }
hasBin: true
+ plist@3.1.0:
+ resolution:
+ {
+ integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==,
+ }
+ engines: { node: '>=10.4.0' }
+
+ plist@3.1.1:
+ resolution:
+ {
+ integrity: sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==,
+ }
+ engines: { node: '>=10.4.0' }
+
possible-typed-array-names@1.1.0:
resolution:
{
@@ -7515,7 +8093,7 @@ packages:
}
engines: { node: ^12 || ^14 || >= 16 }
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: ^8.4.21
postcss-mixins@12.1.2:
resolution:
@@ -7524,7 +8102,7 @@ packages:
}
engines: { node: ^20.0 || ^22.0 || >=24.0 }
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: ^8.2.14
postcss-nested@7.0.2:
resolution:
@@ -7533,7 +8111,7 @@ packages:
}
engines: { node: '>=18.0' }
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: ^8.2.14
postcss-preset-mantine@1.18.0:
resolution:
@@ -7541,7 +8119,7 @@ packages:
integrity: sha512-sP6/s1oC7cOtBdl4mw/IRKmKvYTuzpRrH/vT6v9enMU/EQEQ31eQnHcWtFghOXLH87AAthjL/Q75rLmin1oZoA==,
}
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: '>=8.0.0'
postcss-selector-parser@7.1.1:
resolution:
@@ -7557,7 +8135,7 @@ packages:
}
engines: { node: '>=14.0' }
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: ^8.2.1
postcss-value-parser@4.2.0:
resolution:
@@ -7572,6 +8150,14 @@ packages:
}
engines: { node: ^10 || ^12 || >=14 }
+ postject@1.0.0-alpha.6:
+ resolution:
+ {
+ integrity: sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==,
+ }
+ engines: { node: '>=14.0.0' }
+ hasBin: true
+
powershell-utils@0.1.0:
resolution:
{
@@ -7608,6 +8194,13 @@ packages:
}
engines: { node: '>=18' }
+ proc-log@6.1.0:
+ resolution:
+ {
+ integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==,
+ }
+ engines: { node: ^20.17.0 || >=22.9.0 }
+
process-nextick-args@2.0.1:
resolution:
{
@@ -7627,6 +8220,13 @@ packages:
}
engines: { node: '>=0.4.0' }
+ promise-retry@2.0.1:
+ resolution:
+ {
+ integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==,
+ }
+ engines: { node: '>=10' }
+
prompts@2.4.2:
resolution:
{
@@ -7640,6 +8240,12 @@ packages:
integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==,
}
+ proper-lockfile@4.1.2:
+ resolution:
+ {
+ integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==,
+ }
+
property-information@7.1.0:
resolution:
{
@@ -7848,6 +8454,13 @@ packages:
}
engines: { node: '>=0.10.0' }
+ read-binary-file-arch@1.0.6:
+ resolution:
+ {
+ integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==,
+ }
+ hasBin: true
+
readable-stream@2.3.8:
resolution:
{
@@ -7971,6 +8584,13 @@ packages:
}
engines: { node: '>=0.10.0' }
+ resedit@1.7.2:
+ resolution:
+ {
+ integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==,
+ }
+ engines: { node: '>=12', npm: '>=6' }
+
reselect@5.1.1:
resolution:
{
@@ -8023,6 +8643,13 @@ packages:
}
engines: { node: '>=18' }
+ retry@0.12.0:
+ resolution:
+ {
+ integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==,
+ }
+ engines: { node: '>= 4' }
+
rettime@0.11.11:
resolution:
{
@@ -8042,6 +8669,14 @@ packages:
integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==,
}
+ rimraf@2.6.3:
+ resolution:
+ {
+ integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==,
+ }
+ deprecated: Rimraf versions prior to v4 are no longer supported
+ hasBin: true
+
rimraf@2.7.1:
resolution:
{
@@ -8157,6 +8792,13 @@ packages:
integrity: sha512-2HW7v2ol/uAM7sX4hbD8Z59OGWmAPrvjL8E71UWlBcj6m+kcF6ilQBLny+cIgY214QJeJT5tQuxKKqX0SQqjGQ==,
}
+ sax@1.6.0:
+ resolution:
+ {
+ integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==,
+ }
+ engines: { node: '>=11.0.0' }
+
saxes@5.0.1:
resolution:
{
@@ -8196,6 +8838,13 @@ packages:
integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==,
}
+ semver@5.7.2:
+ resolution:
+ {
+ integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==,
+ }
+ hasBin: true
+
semver@6.3.1:
resolution:
{
@@ -8360,12 +9009,26 @@ packages:
integrity: sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==,
}
+ simple-update-notifier@2.0.0:
+ resolution:
+ {
+ integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==,
+ }
+ engines: { node: '>=10' }
+
sisteransi@1.0.5:
resolution:
{
integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==,
}
+ slice-ansi@3.0.0:
+ resolution:
+ {
+ integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==,
+ }
+ engines: { node: '>=8' }
+
slice-ansi@7.1.2:
resolution:
{
@@ -8380,6 +9043,13 @@ packages:
}
engines: { node: '>=20' }
+ smart-buffer@4.2.0:
+ resolution:
+ {
+ integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==,
+ }
+ engines: { node: '>= 6.0.0', npm: '>= 3.0.0' }
+
sonic-boom@4.2.1:
resolution:
{
@@ -8393,6 +9063,12 @@ packages:
}
engines: { node: '>=0.10.0' }
+ source-map-support@0.5.21:
+ resolution:
+ {
+ integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==,
+ }
+
source-map@0.6.1:
resolution:
{
@@ -8431,6 +9107,13 @@ packages:
integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==,
}
+ stat-mode@1.0.0:
+ resolution:
+ {
+ integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==,
+ }
+ engines: { node: '>= 6' }
+
statuses@2.0.2:
resolution:
{
@@ -8647,7 +9330,7 @@ packages:
}
engines: { node: '>=18.0' }
peerDependencies:
- postcss: '>=8.5.10'
+ postcss: ^8.3.3
sumchecker@3.0.1:
resolution:
@@ -8774,6 +9457,26 @@ packages:
}
engines: { node: '>=6' }
+ tar@7.5.15:
+ resolution:
+ {
+ integrity: sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==,
+ }
+ engines: { node: '>=18' }
+
+ temp-file@3.4.0:
+ resolution:
+ {
+ integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==,
+ }
+
+ temp@0.9.4:
+ resolution:
+ {
+ integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==,
+ }
+ engines: { node: '>=6.0.0' }
+
thread-stream@4.0.0:
resolution:
{
@@ -8781,12 +9484,24 @@ packages:
}
engines: { node: '>=20' }
+ tiny-async-pool@1.3.0:
+ resolution:
+ {
+ integrity: sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==,
+ }
+
tiny-invariant@1.3.3:
resolution:
{
integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==,
}
+ tiny-typed-emitter@2.1.0:
+ resolution:
+ {
+ integrity: sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==,
+ }
+
tinybench@2.9.0:
resolution:
{
@@ -8827,6 +9542,12 @@ packages:
}
hasBin: true
+ tmp-promise@3.0.3:
+ resolution:
+ {
+ integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==,
+ }
+
tmp@0.2.7:
resolution:
{
@@ -9051,6 +9772,13 @@ packages:
integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==,
}
+ undici@6.26.0:
+ resolution:
+ {
+ integrity: sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==,
+ }
+ engines: { node: '>=18.17' }
+
undici@7.25.0:
resolution:
{
@@ -9241,6 +9969,13 @@ packages:
}
engines: { node: '>= 0.8' }
+ verror@1.10.1:
+ resolution:
+ {
+ integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==,
+ }
+ engines: { node: '>=0.6.0' }
+
vfile-message@4.0.3:
resolution:
{
@@ -9471,6 +10206,22 @@ packages:
engines: { node: ^16.13.0 || >=18.0.0 }
hasBin: true
+ which@5.0.0:
+ resolution:
+ {
+ integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==,
+ }
+ engines: { node: ^18.17.0 || >=20.5.0 }
+ hasBin: true
+
+ which@6.0.1:
+ resolution:
+ {
+ integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==,
+ }
+ engines: { node: ^20.17.0 || >=22.9.0 }
+ hasBin: true
+
why-is-node-running@2.3.0:
resolution:
{
@@ -9549,6 +10300,13 @@ packages:
}
engines: { node: '>=4.0' }
+ xmlbuilder@15.1.1:
+ resolution:
+ {
+ integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==,
+ }
+ engines: { node: '>=8.0' }
+
xmlchars@2.2.0:
resolution:
{
@@ -9568,6 +10326,19 @@ packages:
integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==,
}
+ yallist@4.0.0:
+ resolution:
+ {
+ integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==,
+ }
+
+ yallist@5.0.0:
+ resolution:
+ {
+ integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==,
+ }
+ engines: { node: '>=18' }
+
yaml@2.0.0-1:
resolution:
{
@@ -9681,6 +10452,8 @@ packages:
}
snapshots:
+ 7zip-bin@5.2.0: {}
+
'@adobe/css-tools@4.4.4': {}
'@apidevtools/json-schema-ref-parser@9.1.2':
@@ -9961,6 +10734,11 @@ snapshots:
'@csstools/css-tokenizer@4.0.0': {}
+ '@develar/schema-utils@2.6.5':
+ dependencies:
+ ajv: 6.15.0
+ ajv-keywords: 3.5.2(ajv@6.15.0)
+
'@dnd-kit/accessibility@3.1.1(react@19.2.6)':
dependencies:
react: 19.2.6
@@ -10003,6 +10781,18 @@ snapshots:
dependencies:
'@noble/ciphers': 1.3.0
+ '@electron/asar@3.4.1':
+ dependencies:
+ commander: 5.1.0
+ glob: 7.2.3
+ minimatch: 3.1.5
+
+ '@electron/fuses@1.8.0':
+ dependencies:
+ chalk: 4.1.2
+ fs-extra: 9.1.0
+ minimist: 1.2.8
+
'@electron/get@2.0.3':
dependencies:
debug: 4.4.3
@@ -10017,6 +10807,73 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@electron/get@3.1.0':
+ dependencies:
+ debug: 4.4.3
+ env-paths: 2.2.1
+ fs-extra: 8.1.0
+ got: 11.8.6
+ progress: 2.0.3
+ semver: 6.3.1
+ sumchecker: 3.0.1
+ optionalDependencies:
+ global-agent: 3.0.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/notarize@2.5.0':
+ dependencies:
+ debug: 4.4.3
+ fs-extra: 9.1.0
+ promise-retry: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/osx-sign@1.3.3':
+ dependencies:
+ compare-version: 0.1.2
+ debug: 4.4.3
+ fs-extra: 10.1.0
+ isbinaryfile: 4.0.10
+ minimist: 1.2.8
+ plist: 3.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/rebuild@4.0.4':
+ dependencies:
+ '@malept/cross-spawn-promise': 2.0.0
+ debug: 4.4.3
+ node-abi: 4.31.0
+ node-api-version: 0.2.1
+ node-gyp: 12.3.0
+ read-binary-file-arch: 1.0.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/universal@2.0.3':
+ dependencies:
+ '@electron/asar': 3.4.1
+ '@malept/cross-spawn-promise': 2.0.0
+ debug: 4.4.3
+ dir-compare: 4.2.0
+ fs-extra: 11.3.4
+ minimatch: 9.0.9
+ plist: 3.1.0
+ transitivePeerDependencies:
+ - supports-color
+
+ '@electron/windows-sign@1.2.2':
+ dependencies:
+ cross-dirname: 0.1.0
+ debug: 4.4.3
+ fs-extra: 11.3.4
+ minimist: 1.2.8
+ postject: 1.0.0-alpha.6
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
'@emnapi/core@1.10.0':
dependencies:
'@emnapi/wasi-threads': 1.2.1
@@ -10200,7 +11057,7 @@ snapshots:
dependencies:
'@eslint/object-schema': 2.1.7
debug: 4.4.3
- minimatch: 10.2.5
+ minimatch: 3.1.5
transitivePeerDependencies:
- supports-color
@@ -10225,7 +11082,7 @@ snapshots:
ignore: 5.3.2
import-fresh: 3.3.1
js-yaml: 4.1.1
- minimatch: 10.2.5
+ minimatch: 3.1.5
strip-json-comments: 3.1.1
transitivePeerDependencies:
- supports-color
@@ -10329,6 +11186,10 @@ snapshots:
optionalDependencies:
'@types/node': 25.7.0
+ '@isaacs/fs-minipass@4.0.1':
+ dependencies:
+ minipass: 7.1.3
+
'@jridgewell/gen-mapping@0.3.13':
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
@@ -10358,6 +11219,19 @@ snapshots:
'@kwsites/promise-deferred@1.1.1': {}
+ '@malept/cross-spawn-promise@2.0.0':
+ dependencies:
+ cross-spawn: 7.0.6
+
+ '@malept/flatpak-bundler@0.4.0':
+ dependencies:
+ debug: 4.4.3
+ fs-extra: 9.1.0
+ lodash: 4.18.1
+ tmp-promise: 3.0.3
+ transitivePeerDependencies:
+ - supports-color
+
'@mantine/core@9.2.2(@mantine/hooks@9.2.2(react@19.2.6))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@floating-ui/react': 0.27.19(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
@@ -10962,6 +11836,10 @@ snapshots:
'@types/express-serve-static-core': 5.1.1
'@types/serve-static': 2.2.0
+ '@types/fs-extra@9.0.13':
+ dependencies:
+ '@types/node': 25.7.0
+
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -11005,6 +11883,12 @@ snapshots:
dependencies:
undici-types: 7.21.0
+ '@types/plist@3.0.5':
+ dependencies:
+ '@types/node': 25.7.0
+ xmlbuilder: 15.1.1
+ optional: true
+
'@types/qs@6.15.0': {}
'@types/range-parser@1.2.7': {}
@@ -11077,6 +11961,9 @@ snapshots:
'@types/validate-npm-package-name@4.0.2': {}
+ '@types/verror@1.10.11':
+ optional: true
+
'@types/ws@8.18.1':
dependencies:
'@types/node': 25.7.0
@@ -11256,7 +12143,12 @@ snapshots:
convert-source-map: 2.0.0
tinyrainbow: 3.1.0
- '@xmldom/xmldom@0.9.10': {}
+ '@xmldom/xmldom@0.8.13': {}
+
+ '@xmldom/xmldom@0.9.10':
+ optional: true
+
+ abbrev@4.0.0: {}
accepts@2.0.0:
dependencies:
@@ -11275,6 +12167,10 @@ snapshots:
optionalDependencies:
ajv: 8.18.0
+ ajv-keywords@3.5.2(ajv@6.15.0):
+ dependencies:
+ ajv: 6.15.0
+
ajv@6.15.0:
dependencies:
fast-deep-equal: 3.1.3
@@ -11312,6 +12208,51 @@ snapshots:
ansi-styles@6.2.3: {}
+ app-builder-bin@5.0.0-alpha.12: {}
+
+ app-builder-lib@26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1):
+ dependencies:
+ '@develar/schema-utils': 2.6.5
+ '@electron/asar': 3.4.1
+ '@electron/fuses': 1.8.0
+ '@electron/get': 3.1.0
+ '@electron/notarize': 2.5.0
+ '@electron/osx-sign': 1.3.3
+ '@electron/rebuild': 4.0.4
+ '@electron/universal': 2.0.3
+ '@malept/flatpak-bundler': 0.4.0
+ '@types/fs-extra': 9.0.13
+ async-exit-hook: 2.0.1
+ builder-util: 26.8.1
+ builder-util-runtime: 9.5.1
+ chromium-pickle-js: 0.2.0
+ ci-info: 4.3.1
+ debug: 4.4.3
+ dmg-builder: 26.8.1(electron-builder-squirrel-windows@26.8.1)
+ dotenv: 16.6.1
+ dotenv-expand: 11.0.7
+ ejs: 3.1.10
+ electron-builder-squirrel-windows: 26.8.1(dmg-builder@26.8.1)
+ electron-publish: 26.8.1
+ fs-extra: 10.1.0
+ hosted-git-info: 4.1.0
+ isbinaryfile: 5.0.7
+ jiti: 2.7.0
+ js-yaml: 4.1.1
+ json5: 2.2.3
+ lazy-val: 1.0.5
+ minimatch: 10.2.5
+ plist: 3.1.0
+ proper-lockfile: 4.1.2
+ resedit: 1.7.2
+ semver: 7.7.4
+ tar: 7.5.15
+ temp-file: 3.4.0
+ tiny-async-pool: 1.3.0
+ which: 5.0.0
+ transitivePeerDependencies:
+ - supports-color
+
append-field@1.0.0: {}
archiver-utils@2.1.0:
@@ -11421,6 +12362,9 @@ snapshots:
asap@2.0.6: {}
+ assert-plus@1.0.0:
+ optional: true
+
assertion-error@2.0.1: {}
ast-types@0.16.1:
@@ -11433,12 +12377,19 @@ snapshots:
estree-walker: 3.0.3
js-tokens: 10.0.0
+ astral-regex@2.0.0:
+ optional: true
+
+ async-exit-hook@2.0.1: {}
+
async-function@1.0.0: {}
async@3.2.6: {}
asynckit@0.4.0: {}
+ at-least-node@1.0.0: {}
+
atomic-sleep@1.0.0: {}
autoprefixer@10.5.0(postcss@8.5.14):
@@ -11456,6 +12407,8 @@ snapshots:
bail@2.0.2: {}
+ balanced-match@1.0.2: {}
+
balanced-match@4.0.4: {}
base64-js@1.5.1: {}
@@ -11503,6 +12456,15 @@ snapshots:
boolean@3.2.0:
optional: true
+ brace-expansion@1.1.15:
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+
+ brace-expansion@2.1.1:
+ dependencies:
+ balanced-match: 1.0.2
+
brace-expansion@5.0.5:
dependencies:
balanced-match: 4.0.4
@@ -11534,6 +12496,34 @@ snapshots:
buffers@0.1.1: {}
+ builder-util-runtime@9.5.1:
+ dependencies:
+ debug: 4.4.3
+ sax: 1.6.0
+ transitivePeerDependencies:
+ - supports-color
+
+ builder-util@26.8.1:
+ dependencies:
+ 7zip-bin: 5.2.0
+ '@types/debug': 4.1.13
+ app-builder-bin: 5.0.0-alpha.12
+ builder-util-runtime: 9.5.1
+ chalk: 4.1.2
+ cross-spawn: 7.0.6
+ debug: 4.4.3
+ fs-extra: 10.1.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ js-yaml: 4.1.1
+ sanitize-filename: 1.6.4
+ source-map-support: 0.5.21
+ stat-mode: 1.0.0
+ temp-file: 3.4.0
+ tiny-async-pool: 1.3.0
+ transitivePeerDependencies:
+ - supports-color
+
bundle-name@4.1.0:
dependencies:
run-applescript: 7.1.0
@@ -11608,6 +12598,14 @@ snapshots:
character-reference-invalid@2.0.1: {}
+ chownr@3.0.0: {}
+
+ chromium-pickle-js@0.2.0: {}
+
+ ci-info@4.3.1: {}
+
+ ci-info@4.4.0: {}
+
class-variance-authority@0.7.1:
dependencies:
clsx: 2.1.1
@@ -11618,6 +12616,12 @@ snapshots:
cli-spinners@2.9.2: {}
+ cli-truncate@2.1.0:
+ dependencies:
+ slice-ansi: 3.0.0
+ string-width: 4.2.3
+ optional: true
+
cli-truncate@5.2.0:
dependencies:
slice-ansi: 8.0.0
@@ -11657,11 +12661,15 @@ snapshots:
commander@14.0.3: {}
+ commander@5.1.0: {}
+
commander@6.2.0: {}
commander@9.5.0:
optional: true
+ compare-version@0.1.2: {}
+
component-emitter@1.3.1: {}
compress-commons@4.1.2:
@@ -11687,6 +12695,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ concat-map@0.0.1: {}
+
concat-stream@2.0.0:
dependencies:
buffer-from: 1.1.2
@@ -11726,6 +12736,9 @@ snapshots:
cookiejar@2.1.4: {}
+ core-util-is@1.0.2:
+ optional: true
+
core-util-is@1.0.3: {}
cors@2.8.6:
@@ -11749,6 +12762,14 @@ snapshots:
crc-32: 1.2.2
readable-stream: 3.6.2
+ crc@3.8.0:
+ dependencies:
+ buffer: 5.7.1
+ optional: true
+
+ cross-dirname@0.1.0:
+ optional: true
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -11910,6 +12931,36 @@ snapshots:
dingbat-to-unicode@1.0.1: {}
+ dir-compare@4.2.0:
+ dependencies:
+ minimatch: 3.1.5
+ p-limit: 3.1.0
+
+ dmg-builder@26.8.1(electron-builder-squirrel-windows@26.8.1):
+ dependencies:
+ app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
+ builder-util: 26.8.1
+ fs-extra: 10.1.0
+ iconv-lite: 0.6.3
+ js-yaml: 4.1.1
+ optionalDependencies:
+ dmg-license: 1.0.11
+ transitivePeerDependencies:
+ - electron-builder-squirrel-windows
+ - supports-color
+
+ dmg-license@1.0.11:
+ dependencies:
+ '@types/plist': 3.0.5
+ '@types/verror': 1.10.11
+ ajv: 6.15.0
+ crc: 3.8.0
+ iconv-corefoundation: 1.1.7
+ plist: 3.1.1
+ smart-buffer: 4.2.0
+ verror: 1.10.1
+ optional: true
+
doctrine@2.1.0:
dependencies:
esutils: 2.0.3
@@ -11949,6 +13000,12 @@ snapshots:
domelementtype: 2.3.0
domhandler: 5.0.3
+ dotenv-expand@11.0.7:
+ dependencies:
+ dotenv: 16.6.1
+
+ dotenv@16.6.1: {}
+
dotenv@17.4.2: {}
duck@0.1.12:
@@ -11978,8 +13035,63 @@ snapshots:
ee-first@1.1.1: {}
+ ejs@3.1.10:
+ dependencies:
+ jake: 10.9.4
+
+ electron-builder-squirrel-windows@26.8.1(dmg-builder@26.8.1):
+ dependencies:
+ app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
+ builder-util: 26.8.1
+ electron-winstaller: 5.4.0
+ transitivePeerDependencies:
+ - dmg-builder
+ - supports-color
+
+ electron-builder@26.8.1(electron-builder-squirrel-windows@26.8.1):
+ dependencies:
+ app-builder-lib: 26.8.1(dmg-builder@26.8.1)(electron-builder-squirrel-windows@26.8.1)
+ builder-util: 26.8.1
+ builder-util-runtime: 9.5.1
+ chalk: 4.1.2
+ ci-info: 4.4.0
+ dmg-builder: 26.8.1(electron-builder-squirrel-windows@26.8.1)
+ fs-extra: 10.1.0
+ lazy-val: 1.0.5
+ simple-update-notifier: 2.0.0
+ yargs: 17.7.2
+ transitivePeerDependencies:
+ - electron-builder-squirrel-windows
+ - supports-color
+
+ electron-publish@26.8.1:
+ dependencies:
+ '@types/fs-extra': 9.0.13
+ builder-util: 26.8.1
+ builder-util-runtime: 9.5.1
+ chalk: 4.1.2
+ form-data: 4.0.5
+ fs-extra: 10.1.0
+ lazy-val: 1.0.5
+ mime: 2.6.0
+ transitivePeerDependencies:
+ - supports-color
+
electron-to-chromium@1.5.351: {}
+ electron-updater@6.8.3:
+ dependencies:
+ builder-util-runtime: 9.5.1
+ fs-extra: 10.1.0
+ js-yaml: 4.1.1
+ lazy-val: 1.0.5
+ lodash.escaperegexp: 4.1.2
+ lodash.isequal: 4.5.0
+ semver: 7.7.4
+ tiny-typed-emitter: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
electron-vite@5.0.0(vite@7.3.3(@types/node@25.7.0)(jiti@2.7.0)(lightningcss@1.32.0)(sugarss@5.0.1(postcss@8.5.14))(tsx@4.21.0)(yaml@2.9.0)):
dependencies:
'@babel/core': 7.29.0
@@ -11992,6 +13104,18 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ electron-winstaller@5.4.0:
+ dependencies:
+ '@electron/asar': 3.4.1
+ debug: 4.4.3
+ fs-extra: 7.0.1
+ lodash: 4.18.1
+ temp: 0.9.4
+ optionalDependencies:
+ '@electron/windows-sign': 1.2.2
+ transitivePeerDependencies:
+ - supports-color
+
electron@39.8.10:
dependencies:
'@electron/get': 2.0.3
@@ -12025,6 +13149,8 @@ snapshots:
environment@1.1.0: {}
+ err-code@2.0.3: {}
+
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
@@ -12226,7 +13352,7 @@ snapshots:
estraverse: 5.3.0
hasown: 2.0.2
jsx-ast-utils: 3.3.5
- minimatch: 10.2.5
+ minimatch: 3.1.5
object.entries: 1.1.9
object.fromentries: 2.0.8
object.values: 1.2.1
@@ -12280,7 +13406,7 @@ snapshots:
is-glob: 4.0.3
json-stable-stringify-without-jsonify: 1.0.1
lodash.merge: 4.6.2
- minimatch: 10.2.5
+ minimatch: 3.1.5
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
@@ -12365,10 +13491,12 @@ snapshots:
expect-type@1.3.0: {}
+ exponential-backoff@3.1.3: {}
+
express-rate-limit@8.5.0(express@5.2.1):
dependencies:
express: 5.2.1
- ip-address: 10.2.0
+ ip-address: 10.1.0
express-rate-limit@8.5.1(express@5.2.1):
dependencies:
@@ -12424,6 +13552,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ extsprintf@1.4.1:
+ optional: true
+
fast-copy@4.0.3: {}
fast-csv@4.3.6:
@@ -12495,6 +13626,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ filelist@1.0.6:
+ dependencies:
+ minimatch: 5.1.9
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -12552,18 +13687,37 @@ snapshots:
fs-constants@1.0.0: {}
+ fs-extra@10.1.0:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.1
+ universalify: 2.0.1
+
fs-extra@11.3.4:
dependencies:
graceful-fs: 4.2.11
jsonfile: 6.2.1
universalify: 2.0.1
+ fs-extra@7.0.1:
+ dependencies:
+ graceful-fs: 4.2.11
+ jsonfile: 4.0.0
+ universalify: 0.1.2
+
fs-extra@8.1.0:
dependencies:
graceful-fs: 4.2.11
jsonfile: 4.0.0
universalify: 0.1.2
+ fs-extra@9.1.0:
+ dependencies:
+ at-least-node: 1.0.0
+ graceful-fs: 4.2.11
+ jsonfile: 6.2.1
+ universalify: 2.0.1
+
fs.realpath@1.0.0: {}
fsevents@2.3.2:
@@ -12660,7 +13814,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
- minimatch: 10.2.5
+ minimatch: 3.1.5
once: 1.4.0
path-is-absolute: 1.0.1
@@ -12669,7 +13823,7 @@ snapshots:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
- minimatch: 10.2.5
+ minimatch: 3.1.5
once: 1.4.0
path-is-absolute: 1.0.1
@@ -12793,6 +13947,10 @@ snapshots:
hono@4.12.18: {}
+ hosted-git-info@4.1.0:
+ dependencies:
+ lru-cache: 6.0.0
+
html-encoding-sniffer@6.0.0(@noble/hashes@1.8.0):
dependencies:
'@exodus/bytes': 1.15.0(@noble/hashes@1.8.0)
@@ -12820,6 +13978,13 @@ snapshots:
statuses: 2.0.2
toidentifier: 1.0.1
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
http2-wrapper@1.0.3:
dependencies:
quick-lru: 5.1.1
@@ -12838,6 +14003,16 @@ snapshots:
husky@9.1.7: {}
+ iconv-corefoundation@1.1.7:
+ dependencies:
+ cli-truncate: 2.1.0
+ node-addon-api: 1.7.2
+ optional: true
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
iconv-lite@0.7.2:
dependencies:
safer-buffer: 2.1.2
@@ -12880,6 +14055,8 @@ snapshots:
internmap@2.0.3: {}
+ ip-address@10.1.0: {}
+
ip-address@10.2.0: {}
ipaddr.js@1.9.1: {}
@@ -13053,10 +14230,16 @@ snapshots:
isarray@2.0.5: {}
+ isbinaryfile@4.0.10: {}
+
+ isbinaryfile@5.0.7: {}
+
isexe@2.0.0: {}
isexe@3.1.5: {}
+ isexe@4.0.0: {}
+
istanbul-lib-coverage@3.2.2: {}
istanbul-lib-report@3.0.1:
@@ -13079,6 +14262,12 @@ snapshots:
has-symbols: 1.1.0
set-function-name: 2.0.2
+ jake@10.9.4:
+ dependencies:
+ async: 3.2.6
+ filelist: 1.0.6
+ picocolors: 1.1.1
+
jiti@2.7.0: {}
jose@6.2.2: {}
@@ -13207,6 +14396,8 @@ snapshots:
dependencies:
dayjs: 1.11.20
+ lazy-val@1.0.5: {}
+
lazystream@1.0.1:
dependencies:
readable-stream: 2.3.8
@@ -13336,6 +14527,8 @@ snapshots:
lodash.uniq@4.5.0: {}
+ lodash@4.18.1: {}
+
log-symbols@6.0.0:
dependencies:
chalk: 5.6.2
@@ -13375,6 +14568,10 @@ snapshots:
dependencies:
yallist: 3.1.1
+ lru-cache@6.0.0:
+ dependencies:
+ yallist: 4.0.0
+
lucide-react@1.14.0(react@19.2.6):
dependencies:
react: 19.2.6
@@ -13397,7 +14594,7 @@ snapshots:
mammoth@1.12.0:
dependencies:
- '@xmldom/xmldom': 0.9.10
+ '@xmldom/xmldom': 0.8.13
argparse: 1.0.10
base64-js: 1.5.1
bluebird: 3.4.7
@@ -13808,8 +15005,26 @@ snapshots:
dependencies:
brace-expansion: 5.0.5
+ minimatch@3.1.5:
+ dependencies:
+ brace-expansion: 1.1.15
+
+ minimatch@5.1.9:
+ dependencies:
+ brace-expansion: 2.1.1
+
+ minimatch@9.0.9:
+ dependencies:
+ brace-expansion: 2.1.1
+
minimist@1.2.8: {}
+ minipass@7.1.3: {}
+
+ minizlib@3.1.0:
+ dependencies:
+ minipass: 7.1.3
+
mkdirp@0.5.6:
dependencies:
minimist: 1.2.8
@@ -13829,7 +15044,7 @@ snapshots:
headers-polyfill: 5.0.1
is-node-process: 1.2.0
outvariant: 1.4.3
- path-to-regexp: 8.4.2
+ path-to-regexp: 6.3.0
picocolors: 1.1.1
rettime: 0.11.11
statuses: 2.0.2
@@ -13862,8 +15077,19 @@ snapshots:
negotiator@1.0.0: {}
+ node-abi@4.31.0:
+ dependencies:
+ semver: 7.8.0
+
+ node-addon-api@1.7.2:
+ optional: true
+
node-addon-api@8.7.0: {}
+ node-api-version@0.2.1:
+ dependencies:
+ semver: 7.8.0
+
node-domexception@1.0.0: {}
node-exports-info@1.6.0:
@@ -13881,8 +15107,25 @@ snapshots:
node-gyp-build@4.8.4: {}
+ node-gyp@12.3.0:
+ dependencies:
+ env-paths: 2.2.1
+ exponential-backoff: 3.1.3
+ graceful-fs: 4.2.11
+ nopt: 9.0.0
+ proc-log: 6.1.0
+ semver: 7.8.0
+ tar: 7.5.15
+ tinyglobby: 0.2.16
+ undici: 6.26.0
+ which: 6.0.1
+
node-releases@2.0.38: {}
+ nopt@9.0.0:
+ dependencies:
+ abbrev: 4.0.0
+
normalize-path@3.0.0: {}
normalize-url@6.1.0: {}
@@ -14053,10 +15296,14 @@ snapshots:
path-parse@1.0.7: {}
+ path-to-regexp@6.3.0: {}
+
path-to-regexp@8.4.2: {}
pathe@2.0.3: {}
+ pe-library@0.4.1: {}
+
pend@1.2.0: {}
picocolors@1.1.1: {}
@@ -14111,6 +15358,19 @@ snapshots:
optionalDependencies:
fsevents: 2.3.2
+ plist@3.1.0:
+ dependencies:
+ '@xmldom/xmldom': 0.8.13
+ base64-js: 1.5.1
+ xmlbuilder: 15.1.1
+
+ plist@3.1.1:
+ dependencies:
+ '@xmldom/xmldom': 0.9.10
+ base64-js: 1.5.1
+ xmlbuilder: 15.1.1
+ optional: true
+
possible-typed-array-names@1.1.0: {}
postcss-js@4.1.0(postcss@8.5.14):
@@ -14154,6 +15414,11 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
+ postject@1.0.0-alpha.6:
+ dependencies:
+ commander: 9.5.0
+ optional: true
+
powershell-utils@0.1.0: {}
prelude-ls@1.2.1: {}
@@ -14170,12 +15435,19 @@ snapshots:
dependencies:
parse-ms: 4.0.0
+ proc-log@6.1.0: {}
+
process-nextick-args@2.0.1: {}
process-warning@5.0.0: {}
progress@2.0.3: {}
+ promise-retry@2.0.1:
+ dependencies:
+ err-code: 2.0.3
+ retry: 0.12.0
+
prompts@2.4.2:
dependencies:
kleur: 3.0.3
@@ -14187,6 +15459,12 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ proper-lockfile@4.1.2:
+ dependencies:
+ graceful-fs: 4.2.11
+ retry: 0.12.0
+ signal-exit: 3.0.7
+
property-information@7.1.0: {}
proxy-addr@2.0.7:
@@ -14326,6 +15604,12 @@ snapshots:
react@19.2.6: {}
+ read-binary-file-arch@1.0.6:
+ dependencies:
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
readable-stream@2.3.8:
dependencies:
core-util-is: 1.0.3
@@ -14344,7 +15628,7 @@ snapshots:
readdir-glob@1.1.3:
dependencies:
- minimatch: 10.2.5
+ minimatch: 5.1.9
real-require@0.2.0: {}
@@ -14453,6 +15737,10 @@ snapshots:
require-from-string@2.0.2: {}
+ resedit@1.7.2:
+ dependencies:
+ pe-library: 0.4.1
+
reselect@5.1.1: {}
resize-observer-polyfill@1.5.1: {}
@@ -14481,12 +15769,18 @@ snapshots:
onetime: 7.0.0
signal-exit: 4.1.0
+ retry@0.12.0: {}
+
rettime@0.11.11: {}
reusify@1.1.0: {}
rfdc@1.4.1: {}
+ rimraf@2.6.3:
+ dependencies:
+ glob: 7.2.3
+
rimraf@2.7.1:
dependencies:
glob: 7.2.3
@@ -14614,6 +15908,8 @@ snapshots:
parse-srcset: 1.0.2
postcss: 8.5.14
+ sax@1.6.0: {}
+
saxes@5.0.1:
dependencies:
xmlchars: 2.2.0
@@ -14634,6 +15930,8 @@ snapshots:
semver-compare@1.0.0:
optional: true
+ semver@5.7.2: {}
+
semver@6.3.1: {}
semver@7.7.4: {}
@@ -14793,8 +16091,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ simple-update-notifier@2.0.0:
+ dependencies:
+ semver: 7.8.0
+
sisteransi@1.0.5: {}
+ slice-ansi@3.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ astral-regex: 2.0.0
+ is-fullwidth-code-point: 3.0.0
+ optional: true
+
slice-ansi@7.1.2:
dependencies:
ansi-styles: 6.2.3
@@ -14805,12 +16114,20 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
+ smart-buffer@4.2.0:
+ optional: true
+
sonic-boom@4.2.1:
dependencies:
atomic-sleep: 1.0.0
source-map-js@1.2.1: {}
+ source-map-support@0.5.21:
+ dependencies:
+ buffer-from: 1.1.2
+ source-map: 0.6.1
+
source-map@0.6.1: {}
space-separated-tokens@2.0.2: {}
@@ -14824,6 +16141,8 @@ snapshots:
stackback@0.0.2: {}
+ stat-mode@1.0.0: {}
+
statuses@2.0.2: {}
std-env@4.1.0: {}
@@ -15049,12 +16368,36 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
+ tar@7.5.15:
+ dependencies:
+ '@isaacs/fs-minipass': 4.0.1
+ chownr: 3.0.0
+ minipass: 7.1.3
+ minizlib: 3.1.0
+ yallist: 5.0.0
+
+ temp-file@3.4.0:
+ dependencies:
+ async-exit-hook: 2.0.1
+ fs-extra: 10.1.0
+
+ temp@0.9.4:
+ dependencies:
+ mkdirp: 0.5.6
+ rimraf: 2.6.3
+
thread-stream@4.0.0:
dependencies:
real-require: 0.2.0
+ tiny-async-pool@1.3.0:
+ dependencies:
+ semver: 5.7.2
+
tiny-invariant@1.3.3: {}
+ tiny-typed-emitter@2.1.0: {}
+
tinybench@2.9.0: {}
tinyexec@1.1.2: {}
@@ -15072,6 +16415,10 @@ snapshots:
dependencies:
tldts-core: 7.0.30
+ tmp-promise@3.0.3:
+ dependencies:
+ tmp: 0.2.7
+
tmp@0.2.7: {}
to-regex-range@5.0.1:
@@ -15206,6 +16553,8 @@ snapshots:
undici-types@7.21.0: {}
+ undici@6.26.0: {}
+
undici@7.25.0: {}
unicorn-magic@0.3.0: {}
@@ -15312,6 +16661,13 @@ snapshots:
vary@1.1.2: {}
+ verror@1.10.1:
+ dependencies:
+ assert-plus: 1.0.0
+ core-util-is: 1.0.2
+ extsprintf: 1.4.1
+ optional: true
+
vfile-message@4.0.3:
dependencies:
'@types/unist': 3.0.3
@@ -15497,6 +16853,14 @@ snapshots:
dependencies:
isexe: 3.1.5
+ which@5.0.0:
+ dependencies:
+ isexe: 3.1.5
+
+ which@6.0.1:
+ dependencies:
+ isexe: 4.0.0
+
why-is-node-running@2.3.0:
dependencies:
siginfo: 2.0.0
@@ -15535,12 +16899,18 @@ snapshots:
xmlbuilder@10.1.1: {}
+ xmlbuilder@15.1.1: {}
+
xmlchars@2.2.0: {}
y18n@5.0.8: {}
yallist@3.1.1: {}
+ yallist@4.0.0: {}
+
+ yallist@5.0.0: {}
+
yaml@2.0.0-1: {}
yaml@2.9.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index cf85cbe8..8760d8ac 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -5,3 +5,14 @@ packages:
- 'cli'
- 'mcp'
- 'desktop'
+
+overrides:
+ '@xmldom/xmldom': 0.8.13
+ fast-uri: '>=3.1.2'
+ hono: '>=4.12.18'
+ ip-address: '>=10.1.1'
+ postcss: '>=8.5.10'
+ qs: ^6.14.2
+ minimatch: '>=10.2.3'
+ path-to-regexp: '>=8.4.0'
+ tmp: '>=0.2.6'
diff --git a/scripts/desktop-after-pack.mjs b/scripts/desktop-after-pack.mjs
new file mode 100644
index 00000000..f6a6f63d
--- /dev/null
+++ b/scripts/desktop-after-pack.mjs
@@ -0,0 +1,53 @@
+import { cp, rm, stat } from 'node:fs/promises';
+import path from 'node:path';
+
+async function assertDirectory(label, targetPath) {
+ const stats = await stat(targetPath);
+ if (!stats.isDirectory()) {
+ throw new Error(`${label} is not a directory: ${targetPath}`);
+ }
+}
+
+export default async function desktopAfterPack(context) {
+ if (context.electronPlatformName !== 'darwin') {
+ return;
+ }
+
+ const desktopDir = context.packager.projectDir;
+ const stagingDir = path.join(desktopDir, '.desktop-release');
+ const resourcesDir = path.join(
+ context.appOutDir,
+ `${context.packager.appInfo.productFilename}.app`,
+ 'Contents',
+ 'Resources'
+ );
+
+ const stagedServer = path.join(stagingDir, 'server');
+ const stagedWeb = path.join(stagingDir, 'web');
+ const packagedServer = path.join(resourcesDir, 'server');
+ const packagedWeb = path.join(resourcesDir, 'web');
+
+ await Promise.all([
+ assertDirectory('Desktop server staging', stagedServer),
+ assertDirectory('Desktop web staging', stagedWeb),
+ assertDirectory('Packaged resources', resourcesDir),
+ ]);
+
+ await Promise.all([
+ rm(packagedServer, { recursive: true, force: true }),
+ rm(packagedWeb, { recursive: true, force: true }),
+ ]);
+
+ await Promise.all([
+ cp(stagedServer, packagedServer, {
+ recursive: true,
+ force: true,
+ verbatimSymlinks: true,
+ }),
+ cp(stagedWeb, packagedWeb, {
+ recursive: true,
+ force: true,
+ verbatimSymlinks: true,
+ }),
+ ]);
+}
diff --git a/scripts/prepare-desktop-release.mjs b/scripts/prepare-desktop-release.mjs
new file mode 100644
index 00000000..8aad40ef
--- /dev/null
+++ b/scripts/prepare-desktop-release.mjs
@@ -0,0 +1,82 @@
+#!/usr/bin/env node
+import { constants } from 'node:fs';
+import { access, cp, mkdir, rm } from 'node:fs/promises';
+import { spawnSync } from 'node:child_process';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const desktopDir = path.join(rootDir, 'desktop');
+const stagingDir = path.join(desktopDir, '.desktop-release');
+const serverStage = path.join(stagingDir, 'server');
+const webStage = path.join(stagingDir, 'web');
+
+function pnpmCommand() {
+ return process.platform === 'win32' ? 'pnpm.cmd' : 'pnpm';
+}
+
+function run(command, args) {
+ const result = spawnSync(command, args, {
+ cwd: rootDir,
+ encoding: 'utf8',
+ stdio: 'inherit',
+ });
+
+ if (result.error) {
+ throw result.error;
+ }
+
+ if (result.status !== 0) {
+ throw new Error(`${command} ${args.join(' ')} failed with exit code ${result.status}`);
+ }
+}
+
+async function assertExists(label, targetPath) {
+ try {
+ await access(targetPath, constants.F_OK);
+ } catch {
+ throw new Error(`${label} is missing: ${path.relative(rootDir, targetPath)}`);
+ }
+}
+
+async function pruneServerDeploy() {
+ await Promise.all([
+ rm(path.join(serverStage, 'src'), { recursive: true, force: true }),
+ rm(path.join(serverStage, '.veritas-kanban'), { recursive: true, force: true }),
+ rm(path.join(serverStage, 'tsconfig.json'), { force: true }),
+ rm(path.join(serverStage, 'vitest.config.ts'), { force: true }),
+ ]);
+}
+
+async function main() {
+ await assertExists('Server build output', path.join(rootDir, 'server/dist/index.js'));
+ await assertExists('Web build output', path.join(rootDir, 'web/dist/index.html'));
+
+ await rm(stagingDir, { recursive: true, force: true });
+ await mkdir(stagingDir, { recursive: true });
+
+ run(pnpmCommand(), [
+ '--filter',
+ '@veritas-kanban/server',
+ 'deploy',
+ '--prod',
+ serverStage,
+ ]);
+ await pruneServerDeploy();
+
+ await mkdir(webStage, { recursive: true });
+ await cp(path.join(rootDir, 'web/dist'), path.join(webStage, 'dist'), {
+ recursive: true,
+ });
+
+ await assertExists('Packaged server entry', path.join(serverStage, 'dist/index.js'));
+ await assertExists('Packaged server dependencies', path.join(serverStage, 'node_modules'));
+ await assertExists('Packaged web app', path.join(webStage, 'dist/index.html'));
+
+ console.log(`Prepared desktop release staging at ${path.relative(rootDir, stagingDir)}`);
+}
+
+main().catch((error) => {
+ console.error(error instanceof Error ? error.message : error);
+ process.exit(1);
+});
diff --git a/server/src/index.ts b/server/src/index.ts
index d93d1cb2..b33f1fff 100644
--- a/server/src/index.ts
+++ b/server/src/index.ts
@@ -16,6 +16,7 @@ import cors from 'cors';
import cookieParser from 'cookie-parser';
import { WebSocketServer, WebSocket } from 'ws';
import { createServer } from 'http';
+import { readFile } from 'fs/promises';
import os from 'os';
import path from 'path';
import { fileURLToPath } from 'url';
@@ -196,6 +197,7 @@ app.set('etag', 'weak');
// Set CSP_REPORT_URI to a URL to receive violation reports (e.g.,
// https://your-domain.com/csp-report or a service like report-uri.com).
const isDev = process.env.NODE_ENV !== 'production';
+const isDesktopRuntime = process.env.VERITAS_DESKTOP_RUNTIME === '1';
const cspReportOnly = process.env.CSP_REPORT_ONLY === 'true';
const cspReportUri = process.env.CSP_REPORT_URI || null;
@@ -245,7 +247,9 @@ app.use(
frameSrc: ["'none'"],
baseUri: ["'self'"],
formAction: ["'self'"],
- upgradeInsecureRequests: isDev ? null : [],
+ // The packaged desktop app serves the SPA over loopback HTTP. Do not
+ // upgrade those local asset requests to HTTPS.
+ upgradeInsecureRequests: isDev || isDesktopRuntime ? null : [],
// CSP violation reporting — only included when CSP_REPORT_URI is set.
// Works with both enforced and report-only modes.
@@ -486,6 +490,22 @@ app.use('/api', v1Router);
if (process.env.NODE_ENV === 'production') {
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const webDistPath = path.resolve(__dirname, '../../web/dist');
+ const indexHtmlPath = path.join(webDistPath, 'index.html');
+ const injectScriptNonce = (html: string, nonce: string | undefined): string =>
+ nonce ? html.replace(/