diff --git a/.github/workflows/desktop-release-guard.yml b/.github/workflows/desktop-release-guard.yml new file mode 100644 index 000000000..e3d0a569d --- /dev/null +++ b/.github/workflows/desktop-release-guard.yml @@ -0,0 +1,71 @@ +name: Desktop Release Guard + +on: + pull_request: + paths: + - '.github/workflows/desktop-release-guard.yml' + - 'apps/desktop/**' + - 'package.json' + - 'package-lock.json' + - 'packages/client/**' + - 'packages/shared/**' + - 'propr-ui/**' + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: desktop-release-guard-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + verify: + name: Audit and package desktop app + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - name: Checkout repository + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + + - name: Set up Node.js + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6 + with: + node-version-file: '.nvmrc' + cache: npm + cache-dependency-path: package-lock.json + + # Audit the committed resolution before npm lifecycle or packaging code can run. + - name: Audit production runtime dependencies (low threshold) + run: npm run audit:runtime + + - name: Audit desktop packaging toolchain (high threshold) + run: npm run desktop:audit:packaging + + - name: Install locked dependencies + run: npm ci + + - name: Package desktop app from clean checkout + run: | + test ! -e packages/shared/dist + test ! -e packages/client/dist + test ! -e apps/desktop/out + npm run desktop:package + + - name: Typecheck desktop and renderer + run: npm run desktop:typecheck + + - name: Test desktop runtime + run: npm run desktop:test + + - name: Configure Chromium sandbox helper + run: | + sudo chown root:root apps/desktop/out/propr-desktop-linux-x64/chrome-sandbox + sudo chmod 4755 apps/desktop/out/propr-desktop-linux-x64/chrome-sandbox + + - name: Launch packaged desktop app with sandboxing + run: xvfb-run --auto-servernum npm run desktop:smoke diff --git a/.gitignore b/.gitignore index 57baa45eb..5c9139815 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ apps/release-site-videos/ # Standalone publish staging (scripts/build-publish.mjs) dist-publish/ + +# Electron Forge build and package output +apps/desktop/.vite/ +apps/desktop/out/ diff --git a/apps/desktop/README.md b/apps/desktop/README.md new file mode 100644 index 000000000..265883486 --- /dev/null +++ b/apps/desktop/README.md @@ -0,0 +1,51 @@ +# ProPR Desktop + +This workspace packages the existing `propr-ui` React source as a sandboxed Electron renderer. The desktop entry is +`propr-ui/src/desktop.tsx`; the normal web entry, service worker, CLI, API, and self-hosted deployment remain unchanged. + +## Commands + +Run these from the repository root: + +```sh +npm run desktop:dev +npm run desktop:typecheck +npm run desktop:test +npm run desktop:package +npm run desktop:smoke # Run under xvfb-run on a headless Linux host. +npm run desktop:make +npm run desktop:audit +# On Linux hosts with the corresponding native packaging tools installed: +npm run make:deb -w @propr/desktop +npm run make:rpm -w @propr/desktop +``` + +Desktop development, typecheck, package, and make commands build required renderer workspace dependencies through +`desktop:prepare`, in dependency order (`@propr/shared` then `@propr/client`). They do not depend on previously +generated workspace `dist` directories. + +Development renderer URLs are accepted only when Electron Forge supplies an HTTP loopback URL. Packaged builds load +the generated renderer from the application ASAR through an app-owned protocol. + +The packaged-binary smoke test verifies the hardened fuse states, launches the Linux artifact without a +sandbox-disabling flag, rejects main-process uncaught exceptions, and requires proof that `window.proprDesktop` is +exposed before accepting renderer-ready and a clean exit. + +`desktop:audit` deliberately applies separate policies to the two dependency surfaces: low-or-higher advisories fail +the production-runtime audit, while high and critical advisories fail the desktop development/build-tool audit. Release +CI runs both checks directly from the committed lockfile before installing or executing the packaging toolchain. + +## Security boundary + +The renderer has no Node.js integration and receives only the typed `window.proprDesktop` bridge. It exposes metadata, +validated external-browser opening, profiles, encrypted credentials, lifecycle placeholders, and validated deep-link +events. It never exposes a shell, command runner, arbitrary IPC call, or filesystem path/API. + +Profile metadata is stored in an app-owned, permission-restricted JSON file. Credential values are encrypted with +Electron `safeStorage` before they are written separately. If OS encryption is unavailable—or Linux selects the +`basic_text` backend—the app reports that state and refuses to persist or return credentials; there is no plaintext +fallback. Profiles remain usable because they contain only a display label and validated API endpoint. + +`propr://connect` and `propr://open` are the only accepted deep-link actions. A single-instance lock routes later +activations to the existing window. Local lifecycle methods intentionally return `not-implemented`; this scaffold does +not download, install, start, or execute ProPR runtime components. diff --git a/apps/desktop/forge.config.ts b/apps/desktop/forge.config.ts new file mode 100644 index 000000000..a2d291851 --- /dev/null +++ b/apps/desktop/forge.config.ts @@ -0,0 +1,56 @@ +import type { ForgeConfig } from '@electron-forge/shared-types'; +import { MakerDeb } from '@electron-forge/maker-deb'; +import { MakerRpm } from '@electron-forge/maker-rpm'; +import { MakerSquirrel } from '@electron-forge/maker-squirrel'; +import { MakerZIP } from '@electron-forge/maker-zip'; +import { VitePlugin } from '@electron-forge/plugin-vite'; +import { flipFuses, FuseV1Options, FuseVersion } from '@electron/fuses'; +import { resolve } from 'node:path'; + +const config: ForgeConfig = { + packagerConfig: { + asar: true, + name: 'propr-desktop', + executableName: 'propr-desktop', + }, + rebuildConfig: {}, + hooks: { + packageAfterCopy: async (_forgeConfig, resourcesPath, _electronVersion, platform, arch) => { + const applePlatform = platform === 'darwin' || platform === 'mas'; + const executableName = applePlatform ? 'Electron' : `electron${platform === 'win32' ? '.exe' : ''}`; + await flipFuses(resolve(resourcesPath, '..', '..', applePlatform ? 'MacOS' : '', executableName), { + version: FuseVersion.V1, + resetAdHocDarwinSignature: applePlatform && arch === 'arm64', + strictlyRequireAllFuses: true, + [FuseV1Options.RunAsNode]: false, + [FuseV1Options.EnableCookieEncryption]: true, + [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false, + [FuseV1Options.EnableNodeCliInspectArguments]: false, + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true, + [FuseV1Options.OnlyLoadAppFromAsar]: true, + [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot]: false, + [FuseV1Options.GrantFileProtocolExtraPrivileges]: false, + [FuseV1Options.WasmTrapHandlers]: true, + }); + }, + }, + makers: [ + new MakerSquirrel({ name: 'propr_desktop' }), + new MakerZIP({}, ['darwin', 'linux']), + ...(process.env.PROPR_DESKTOP_ENABLE_DEB === '1' ? [new MakerDeb({})] : []), + ...(process.env.PROPR_DESKTOP_ENABLE_RPM === '1' ? [new MakerRpm({})] : []), + ], + plugins: [ + new VitePlugin({ + build: [ + { entry: 'src/main.ts', config: 'vite.main.config.ts' }, + { entry: 'src/preload.ts', config: 'vite.preload.config.ts' }, + ], + renderer: [ + { name: 'main_window', config: 'vite.renderer.config.ts' }, + ], + }), + ], +}; + +export default config; diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 000000000..c82d40083 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,45 @@ +{ + "name": "@propr/desktop", + "productName": "ProPR Desktop", + "version": "0.8.15", + "private": true, + "description": "Secure ProPR desktop application", + "author": "Unchained Development OÜ / Rinalds Uzkalns", + "license": "Apache-2.0", + "homepage": "https://github.com/integry/propr", + "type": "module", + "main": ".vite/build/main.cjs", + "scripts": { + "prepare:renderer": "npm run build -w @propr/shared && npm run build -w @propr/client", + "predev": "npm run prepare:renderer", + "dev": "electron-forge start", + "pretypecheck": "npm run prepare:renderer", + "typecheck": "tsc --noEmit", + "test": "tsx --test src/**/*.test.ts", + "prepackage": "npm run prepare:renderer", + "package": "electron-forge package", + "smoke:package": "node scripts/smoke-packaged.mjs", + "premake": "npm run prepare:renderer", + "make": "electron-forge make", + "premake:deb": "npm run prepare:renderer", + "make:deb": "PROPR_DESKTOP_ENABLE_DEB=1 electron-forge make --targets @electron-forge/maker-deb", + "premake:rpm": "npm run prepare:renderer", + "make:rpm": "PROPR_DESKTOP_ENABLE_RPM=1 electron-forge make --targets @electron-forge/maker-rpm" + }, + "devDependencies": { + "@electron-forge/cli": "8.0.0-alpha.10", + "@electron-forge/maker-deb": "8.0.0-alpha.10", + "@electron-forge/maker-rpm": "8.0.0-alpha.10", + "@electron-forge/maker-squirrel": "8.0.0-alpha.10", + "@electron-forge/maker-zip": "8.0.0-alpha.10", + "@electron-forge/plugin-vite": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10", + "@electron/fuses": "^2.1.3", + "@types/node": "^22.10.0", + "@vitejs/plugin-react": "^4.6.0", + "electron": "^44.0.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite": "^7.3.5" + } +} diff --git a/apps/desktop/renderer.html b/apps/desktop/renderer.html new file mode 100644 index 000000000..374512fdf --- /dev/null +++ b/apps/desktop/renderer.html @@ -0,0 +1,17 @@ + + + + + + + + ProPR Desktop + + +
+ + + diff --git a/apps/desktop/scripts/smoke-packaged.mjs b/apps/desktop/scripts/smoke-packaged.mjs new file mode 100644 index 000000000..ed36bb5a3 --- /dev/null +++ b/apps/desktop/scripts/smoke-packaged.mjs @@ -0,0 +1,146 @@ +import { spawn } from 'node:child_process'; +import { once } from 'node:events'; +import { access, mkdtemp, rm } from 'node:fs/promises'; +import { createServer } from 'node:http'; +import { tmpdir } from 'node:os'; +import { resolve } from 'node:path'; +import { DESKTOP_RENDERER_ORIGIN } from '@propr/shared'; +import { + FuseState, + FuseV1Options, + FuseVersion, + getCurrentFuseWire, +} from '@electron/fuses'; + +const READY_EVENT = 'desktop.renderer.ready'; +const PRELOAD_BRIDGE_PROOF = '"preloadBridgeExposed":true'; +const PROFILE_API_PROOF = 'desktop.renderer.profile_api.ready'; +const MAIN_PROCESS_ERROR_MARKERS = [ + 'desktop.main_process.uncaught_exception', + 'A JavaScript error occurred in the main process', + 'Uncaught Exception:', +]; +const TIMEOUT_MS = 30_000; +const binaryPath = resolve('out', `propr-desktop-linux-${process.arch}`, 'propr-desktop'); + +if (process.platform !== 'linux') { + throw new Error('The packaged-binary smoke test currently targets the Linux artifact'); +} + +await access(binaryPath); + +const expectedFuses = new Map([ + [FuseV1Options.RunAsNode, FuseState.DISABLE], + [FuseV1Options.EnableCookieEncryption, FuseState.ENABLE], + [FuseV1Options.EnableNodeOptionsEnvironmentVariable, FuseState.DISABLE], + [FuseV1Options.EnableNodeCliInspectArguments, FuseState.DISABLE], + [FuseV1Options.EnableEmbeddedAsarIntegrityValidation, FuseState.ENABLE], + [FuseV1Options.OnlyLoadAppFromAsar, FuseState.ENABLE], + [FuseV1Options.LoadBrowserProcessSpecificV8Snapshot, FuseState.DISABLE], + [FuseV1Options.GrantFileProtocolExtraPrivileges, FuseState.DISABLE], + [FuseV1Options.WasmTrapHandlers, FuseState.ENABLE], +]); +const actualFuses = await getCurrentFuseWire(binaryPath); + +if (actualFuses.version !== FuseVersion.V1) { + throw new Error(`Expected fuse wire version ${FuseVersion.V1}, received ${actualFuses.version}`); +} +for (const [fuse, expectedState] of expectedFuses) { + const actualState = actualFuses[fuse]; + if (actualState !== expectedState) { + throw new Error( + `Unexpected ${FuseV1Options[fuse]} fuse state: expected ${FuseState[expectedState]}, received ${FuseState[actualState] ?? actualState}`, + ); + } +} + +const userDataPath = await mkdtemp(resolve(tmpdir(), 'propr-desktop-smoke-')); +const launchArguments = ['--disable-gpu', `--user-data-dir=${userDataPath}`]; +if (launchArguments.some(argument => argument === '--no-sandbox' || argument === '--disable-sandbox')) { + throw new Error('The packaged-binary smoke test must not disable Electron sandboxing'); +} + +let output = ''; +let receivedProfileApiOrigin; +const profileApiServer = createServer((request, response) => { + receivedProfileApiOrigin = request.headers.origin; + if ( + request.method !== 'GET' + || request.url !== '/api/compatibility' + || receivedProfileApiOrigin !== DESKTOP_RENDERER_ORIGIN + ) { + response.writeHead(403, { 'Content-Type': 'application/json' }); + response.end('{"error":"CORS origin rejected"}'); + return; + } + response.writeHead(200, { + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Allow-Origin': DESKTOP_RENDERER_ORIGIN, + 'Content-Type': 'application/json', + }); + response.end('{"profileEndpoint":true}'); +}); +profileApiServer.listen(0, '127.0.0.1'); +await once(profileApiServer, 'listening'); +const profileApiAddress = profileApiServer.address(); +if (!profileApiAddress || typeof profileApiAddress === 'string') { + throw new Error('Packaged desktop smoke profile API did not bind to a TCP port'); +} +const profileApiUrl = `http://127.0.0.1:${profileApiAddress.port}`; + +try { + const child = spawn(binaryPath, launchArguments, { + env: { + ...process.env, + PROPR_DESKTOP_SMOKE_PROFILE_API_URL: profileApiUrl, + PROPR_DESKTOP_SMOKE_TEST: '1', + }, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + const capture = chunk => { + const text = chunk.toString(); + output += text; + process.stdout.write(text); + }; + child.stdout.on('data', capture); + child.stderr.on('data', capture); + + const result = await new Promise((resolveResult, reject) => { + const timeout = setTimeout(() => { + child.kill('SIGKILL'); + reject(new Error(`Packaged desktop did not reach renderer-ready within ${TIMEOUT_MS / 1000} seconds`)); + }, TIMEOUT_MS); + child.once('error', error => { + clearTimeout(timeout); + reject(error); + }); + child.once('close', (code, signal) => { + clearTimeout(timeout); + resolveResult({ code, signal }); + }); + }); + + const mainProcessError = MAIN_PROCESS_ERROR_MARKERS.find(marker => output.includes(marker)); + if (mainProcessError) { + throw new Error(`Packaged desktop reported a main-process uncaught exception (${mainProcessError})`); + } + if (result.code !== 0) { + throw new Error(`Packaged desktop exited with code ${result.code ?? 'null'} (signal ${result.signal ?? 'none'})`); + } + if (!output.includes(READY_EVENT)) { + throw new Error('Packaged desktop exited without reporting renderer-ready'); + } + if (!output.includes(PRELOAD_BRIDGE_PROOF)) { + throw new Error('Packaged desktop reported renderer-ready without proving window.proprDesktop is exposed'); + } + if (!output.includes(PROFILE_API_PROOF) || receivedProfileApiOrigin !== DESKTOP_RENDERER_ORIGIN) { + throw new Error('Packaged desktop did not complete a profile API request from its exact renderer origin'); + } + + console.log('Packaged Linux desktop reached renderer-ready and completed a profile API request with sandboxing enabled.'); +} finally { + profileApiServer.closeAllConnections(); + await new Promise(resolveClose => profileApiServer.close(resolveClose)); + await rm(userDataPath, { recursive: true, force: true }); +} diff --git a/apps/desktop/src/deep-link-delivery.test.ts b/apps/desktop/src/deep-link-delivery.test.ts new file mode 100644 index 000000000..171209700 --- /dev/null +++ b/apps/desktop/src/deep-link-delivery.test.ts @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { DeepLinkDelivery, type DeepLinkWindow } from './deep-link-delivery'; + +describe('desktop deep-link delivery', () => { + it('delivers a link received after did-finish-load but before global window assignment', () => { + const sent: Array<{ channel: string; value: string }> = []; + const window: DeepLinkWindow = { + isDestroyed: () => false, + webContents: { + isLoading: () => false, + send: (channel, value) => sent.push({ channel, value }), + }, + }; + const delivery = new DeepLinkDelivery('desktop:deep-link', ['propr://open?task=initial']); + + delivery.didFinishLoad(window); + delivery.deliver('propr://open?task=between'); + delivery.setWindow(window); + + assert.deepEqual(sent, [ + { channel: 'desktop:deep-link', value: 'propr://open?task=initial' }, + { channel: 'desktop:deep-link', value: 'propr://open?task=between' }, + ]); + }); +}); diff --git a/apps/desktop/src/deep-link-delivery.ts b/apps/desktop/src/deep-link-delivery.ts new file mode 100644 index 000000000..99c124632 --- /dev/null +++ b/apps/desktop/src/deep-link-delivery.ts @@ -0,0 +1,44 @@ +export interface DeepLinkWindow { + isDestroyed(): boolean; + webContents: { + isLoading(): boolean; + send(channel: string, value: string): void; + }; +} + +/** Coordinates protocol delivery across the window creation/load boundary. */ +export class DeepLinkDelivery { + private window: TWindow | null = null; + + constructor( + private readonly channel: string, + private readonly pending: string[] = [], + ) {} + + deliver(value: string): void { + if (!this.window || this.window.isDestroyed() || this.window.webContents.isLoading()) { + this.pending.push(value); + return; + } + this.window.webContents.send(this.channel, value); + } + + didFinishLoad(window: TWindow): void { + this.flush(window); + } + + setWindow(window: TWindow): void { + this.window = window; + this.flush(window); + } + + clearWindow(window: TWindow): void { + if (this.window === window) this.window = null; + } + + private flush(window: TWindow): void { + if (window.isDestroyed() || window.webContents.isLoading()) return; + const linksToDeliver = this.pending.splice(0); + linksToDeliver.forEach(value => window.webContents.send(this.channel, value)); + } +} diff --git a/apps/desktop/src/desktop-session.ts b/apps/desktop/src/desktop-session.ts new file mode 100644 index 000000000..1beb2fd79 --- /dev/null +++ b/apps/desktop/src/desktop-session.ts @@ -0,0 +1,18 @@ +import type { Session } from 'electron'; +import { normalizeApiBaseUrl } from './security'; + +export const logoutDesktopSession = async ( + desktopSession: Pick, + apiBaseUrl: unknown, +): Promise => { + if (typeof apiBaseUrl !== 'string') throw new Error('Invalid desktop API URL'); + const normalizedApiBaseUrl = normalizeApiBaseUrl(apiBaseUrl); + if (!normalizedApiBaseUrl || normalizedApiBaseUrl !== apiBaseUrl) throw new Error('Invalid desktop API URL'); + const response = await desktopSession.fetch(`${normalizedApiBaseUrl}/api/auth/logout`, { + credentials: 'include', + redirect: 'manual', + }); + if (!response.ok && (response.status < 300 || response.status >= 400)) { + throw new Error(`Desktop logout failed with HTTP ${response.status}`); + } +}; diff --git a/apps/desktop/src/global.d.ts b/apps/desktop/src/global.d.ts new file mode 100644 index 000000000..ad7963f08 --- /dev/null +++ b/apps/desktop/src/global.d.ts @@ -0,0 +1,2 @@ +declare const MAIN_WINDOW_VITE_DEV_SERVER_URL: string | undefined; +declare const MAIN_WINDOW_VITE_NAME: string; diff --git a/apps/desktop/src/ipc.test.ts b/apps/desktop/src/ipc.test.ts new file mode 100644 index 000000000..8ac15b68b --- /dev/null +++ b/apps/desktop/src/ipc.test.ts @@ -0,0 +1,37 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import type { Session } from 'electron'; +import { logoutDesktopSession } from './desktop-session'; + +describe('desktop session IPC operations', () => { + it('logs out through the active Electron session with credentials and without following redirects', async () => { + const requests: Array<{ url: string; init: RequestInit | undefined }> = []; + const desktopSession: Pick = { + fetch: async (input, init) => { + requests.push({ url: input.toString(), init }); + return new Response(null, { status: 302 }); + }, + }; + + await logoutDesktopSession(desktopSession, 'https://propr.example.com'); + + assert.deepEqual(requests, [{ + url: 'https://propr.example.com/api/auth/logout', + init: { credentials: 'include', redirect: 'manual' }, + }]); + }); + + it('rejects untrusted logout endpoints before making a session request', async () => { + let requested = false; + const desktopSession: Pick = { + fetch: async () => { + requested = true; + return new Response(null, { status: 200 }); + }, + }; + + await assert.rejects(logoutDesktopSession(desktopSession, 'https://propr.example.com/base'), /Invalid desktop API URL/); + await assert.rejects(logoutDesktopSession(desktopSession, 'https://user:secret@example.com'), /Invalid desktop API URL/); + assert.equal(requested, false); + }); +}); diff --git a/apps/desktop/src/ipc.ts b/apps/desktop/src/ipc.ts new file mode 100644 index 000000000..93245534b --- /dev/null +++ b/apps/desktop/src/ipc.ts @@ -0,0 +1,67 @@ +import type { App, IpcMain, IpcMainInvokeEvent, Session } from 'electron'; +import { shell } from 'electron'; +import { logoutDesktopSession } from './desktop-session'; +import type { DesktopLogger } from './logger'; +import type { LocalLifecycleController } from './lifecycle'; +import type { ProfileStore } from './profile-store'; +import { isSafeExternalUrl, isTrustedRendererUrl } from './security'; +import { IPC_CHANNELS } from './shared/contract'; + +interface RegisterIpcOptions { + app: App; + ipcMain: IpcMain; + profiles: ProfileStore; + lifecycle: LocalLifecycleController; + logger: DesktopLogger; + desktopSession: Session; + devServerUrl: string | undefined; + packagedRendererUrl: string; +} + +type Handler = (event: IpcMainInvokeEvent, ...args: any[]) => unknown; + +export const registerIpcHandlers = (options: RegisterIpcOptions): void => { + const trusted = (event: IpcMainInvokeEvent): boolean => { + const senderUrl = event.senderFrame?.url ?? ''; + return isTrustedRendererUrl(senderUrl, options.devServerUrl, options.packagedRendererUrl); + }; + const handle = (channel: string, handler: Handler): void => { + options.ipcMain.handle(channel, async (event, ...args) => { + if (!trusted(event)) { + options.logger.log('warn', 'desktop.ipc.rejected', { channel }); + throw new Error('Untrusted desktop IPC sender'); + } + try { + return await handler(event, ...args); + } catch (error) { + options.logger.log('error', 'desktop.ipc.failed', { channel, error }); + throw error; + } + }); + }; + + handle(IPC_CHANNELS.appMetadata, () => ({ + name: options.app.getName(), + version: options.app.getVersion(), + platform: process.platform, + arch: process.arch, + packaged: options.app.isPackaged, + })); + handle(IPC_CHANNELS.authLogout, (_event, apiBaseUrl) => logoutDesktopSession(options.desktopSession, apiBaseUrl)); + handle(IPC_CHANNELS.openExternal, async (_event, value: unknown) => { + if (typeof value !== 'string' || !isSafeExternalUrl(value)) throw new Error('External URL is not allowed'); + await shell.openExternal(value); + }); + handle(IPC_CHANNELS.storageSecurity, () => options.profiles.security()); + handle(IPC_CHANNELS.profilesList, () => options.profiles.list()); + handle(IPC_CHANNELS.profilesSave, (_event, input) => options.profiles.save(input)); + handle(IPC_CHANNELS.profilesRemove, (_event, profileId) => options.profiles.remove(profileId)); + handle(IPC_CHANNELS.profilesSetActive, (_event, profileId) => options.profiles.setActive(profileId)); + handle(IPC_CHANNELS.credentialsRead, (_event, profileId) => options.profiles.readCredential(profileId)); + handle(IPC_CHANNELS.credentialsWrite, (_event, profileId, value) => options.profiles.writeCredential(profileId, value)); + handle(IPC_CHANNELS.credentialsRemove, (_event, profileId) => options.profiles.removeCredential(profileId)); + handle(IPC_CHANNELS.lifecycleStatus, () => options.lifecycle.status()); + handle(IPC_CHANNELS.lifecycleStart, () => options.lifecycle.start()); + handle(IPC_CHANNELS.lifecycleStop, () => options.lifecycle.stop()); + handle(IPC_CHANNELS.lifecycleRestart, () => options.lifecycle.restart()); +}; diff --git a/apps/desktop/src/lifecycle.ts b/apps/desktop/src/lifecycle.ts new file mode 100644 index 000000000..a302635fc --- /dev/null +++ b/apps/desktop/src/lifecycle.ts @@ -0,0 +1,40 @@ +import type { LocalLifecycleOperationResult, LocalLifecycleStatus } from './shared/contract'; + +/** + * Stable renderer-facing lifecycle boundary. Runtime installation and process + * control are deliberately absent until the user-approved setup work lands. + */ +export class LocalLifecycleController { + #status: LocalLifecycleStatus = { state: 'disconnected' }; + + status(): LocalLifecycleStatus { + return { ...this.#status }; + } + + start(): LocalLifecycleOperationResult { + return this.#unsupported(); + } + + stop(): LocalLifecycleOperationResult { + return this.#unsupported(); + } + + restart(): LocalLifecycleOperationResult { + return this.#unsupported(); + } + + async shutdown(): Promise { + this.#status = { state: 'disconnected' }; + } + + #unsupported(): LocalLifecycleOperationResult { + return { + ok: false, + code: 'not-implemented', + status: { + ...this.#status, + detail: 'Local runtime management is not available in this desktop scaffold.', + }, + }; + } +} diff --git a/apps/desktop/src/logger.ts b/apps/desktop/src/logger.ts new file mode 100644 index 000000000..a50fd9bbe --- /dev/null +++ b/apps/desktop/src/logger.ts @@ -0,0 +1,33 @@ +import { appendFile, mkdir } from 'node:fs/promises'; +import { dirname } from 'node:path'; + +export type LogLevel = 'debug' | 'info' | 'warn' | 'error'; + +export interface DesktopLogger { + log(level: LogLevel, event: string, fields?: Record): void; +} + +const serializeError = (value: unknown): unknown => value instanceof Error + ? { name: value.name, message: value.message, stack: value.stack } + : value; + +export const createDesktopLogger = (logPath: string): DesktopLogger => { + let pending = Promise.resolve(); + const log = (level: LogLevel, event: string, fields: Record = {}) => { + const record = JSON.stringify({ + timestamp: new Date().toISOString(), + level, + event, + ...Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, serializeError(value)])), + }); + const consoleMethod = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log; + consoleMethod(record); + pending = pending + .then(async () => { + await mkdir(dirname(logPath), { recursive: true, mode: 0o700 }); + await appendFile(logPath, `${record}\n`, { encoding: 'utf8', mode: 0o600 }); + }) + .catch(error => console.error(JSON.stringify({ level: 'error', event: 'desktop.log.write_failed', error: serializeError(error) }))); + }; + return { log }; +}; diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts new file mode 100644 index 000000000..d121bd8d8 --- /dev/null +++ b/apps/desktop/src/main.ts @@ -0,0 +1,261 @@ +import { isAbsolute, join, relative, resolve } from 'node:path'; +import { pathToFileURL } from 'node:url'; +import { app, BrowserWindow, ipcMain, net, protocol, safeStorage, session, shell } from 'electron'; +import { DESKTOP_RENDERER_ORIGIN } from '@propr/shared'; +import { DeepLinkDelivery } from './deep-link-delivery'; +import { registerIpcHandlers } from './ipc'; +import { LocalLifecycleController } from './lifecycle'; +import { createDesktopLogger, type DesktopLogger } from './logger'; +import { ProfileStore, type EncryptionProvider } from './profile-store'; +import { + deepLinkFromArguments, + isSafeExternalUrl, + isTrustedRendererUrl, + normalizeApiBaseUrl, + normalizeDeepLink, + rendererContentSecurityPolicy, + validatedDevServerUrl, +} from './security'; +import { DESKTOP_PROTOCOL, IPC_CHANNELS } from './shared/contract'; +import { createBrowserWindowOptions } from './window-options'; + +const devServerUrl = typeof MAIN_WINDOW_VITE_DEV_SERVER_URL === 'string' + ? MAIN_WINDOW_VITE_DEV_SERVER_URL + : undefined; +const PACKAGED_RENDERER_SCHEME = 'propr-app'; +const PACKAGED_RENDERER_HOST = 'renderer'; +const packagedRendererRoot = join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}`); +const packagedRendererUrl = `${DESKTOP_RENDERER_ORIGIN}/renderer.html`; +let mainWindow: BrowserWindow | null = null; +const initialDeepLink = deepLinkFromArguments(process.argv); +const deepLinkDelivery = new DeepLinkDelivery( + IPC_CHANNELS.deepLink, + initialDeepLink ? [initialDeepLink] : [], +); +let logger: DesktopLogger | null = null; +let shutdownStarted = false; + +const log = (level: 'debug' | 'info' | 'warn' | 'error', event: string, fields?: Record) => + logger + ? logger.log(level, event, fields) + : console.error(JSON.stringify({ timestamp: new Date().toISOString(), level, event, ...fields })); + +process.on('uncaughtExceptionMonitor', error => { + log('error', 'desktop.main_process.uncaught_exception', { error }); +}); + +protocol.registerSchemesAsPrivileged([{ + scheme: PACKAGED_RENDERER_SCHEME, + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + corsEnabled: true, + }, +}]); + +const registerProtocolClient = (): void => { + if (process.defaultApp && process.argv[1]) { + app.setAsDefaultProtocolClient(DESKTOP_PROTOCOL, process.execPath, [process.argv[1]]); + return; + } + app.setAsDefaultProtocolClient(DESKTOP_PROTOCOL); +}; + +const deliverDeepLink = (value: string): void => { + deepLinkDelivery.deliver(value); +}; + +const configureSessionSecurity = (): void => { + const desktopSession = session.defaultSession; + desktopSession.setPermissionCheckHandler(() => false); + desktopSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false)); + desktopSession.webRequest.onHeadersReceived((details, callback) => { + callback({ + responseHeaders: { + ...details.responseHeaders, + 'Content-Security-Policy': [rendererContentSecurityPolicy(!app.isPackaged)], + }, + }); + }); +}; + +const configurePackagedRendererProtocol = (): void => { + protocol.handle(PACKAGED_RENDERER_SCHEME, request => { + const requestUrl = new URL(request.url); + if (requestUrl.hostname !== PACKAGED_RENDERER_HOST) { + return new Response(null, { status: 404 }); + } + + let requestedPath: string; + try { + requestedPath = decodeURIComponent(requestUrl.pathname).replace(/^\/+/, ''); + } catch { + return new Response(null, { status: 400 }); + } + const filePath = resolve(packagedRendererRoot, requestedPath); + const relativePath = relative(packagedRendererRoot, filePath); + if (relativePath.startsWith('..') || isAbsolute(relativePath)) { + return new Response(null, { status: 403 }); + } + return net.fetch(pathToFileURL(filePath).href); + }); +}; + +const openAllowedExternalUrl = async (url: string): Promise => { + if (!isSafeExternalUrl(url)) { + log('warn', 'desktop.external_url.rejected'); + return; + } + await shell.openExternal(url); +}; + +const createMainWindow = async (): Promise => { + const window = new BrowserWindow(createBrowserWindowOptions(join(__dirname, 'preload.cjs'), !app.isPackaged)); + const readyToShow = new Promise(resolveReady => window.once('ready-to-show', resolveReady)); + + window.webContents.setWindowOpenHandler(({ url }) => { + void openAllowedExternalUrl(url); + return { action: 'deny' }; + }); + window.webContents.on('will-navigate', (event, url) => { + if (isTrustedRendererUrl(url, devServerUrl, packagedRendererUrl)) return; + event.preventDefault(); + void openAllowedExternalUrl(url); + }); + window.webContents.on('will-attach-webview', (event) => event.preventDefault()); + window.webContents.on('render-process-gone', (_event, details) => { + log('error', 'desktop.renderer.gone', { reason: details.reason, exitCode: details.exitCode }); + }); + window.webContents.on('did-finish-load', () => { + deepLinkDelivery.didFinishLoad(window); + }); + window.on('closed', () => { + if (mainWindow === window) { + mainWindow = null; + deepLinkDelivery.clearWindow(window); + } + }); + + const validatedDevUrl = validatedDevServerUrl(devServerUrl); + if (devServerUrl && !validatedDevUrl) throw new Error('Electron Forge supplied an unsafe renderer development URL'); + if (validatedDevUrl) { + await window.loadURL(new URL('renderer.html', validatedDevUrl).href); + } else { + await window.loadURL(packagedRendererUrl); + } + + await readyToShow; + const preloadBridgeExposed = await window.webContents.executeJavaScript( + "typeof window.proprDesktop === 'object' && window.proprDesktop !== null", + ); + if (preloadBridgeExposed !== true) { + throw new Error('Desktop preload bridge was not exposed to the renderer'); + } + const smokeProfileApiUrl = process.env.PROPR_DESKTOP_SMOKE_PROFILE_API_URL; + if (app.isPackaged && process.env.PROPR_DESKTOP_SMOKE_TEST === '1' && smokeProfileApiUrl) { + const normalizedSmokeApiUrl = normalizeApiBaseUrl(smokeProfileApiUrl); + if (!normalizedSmokeApiUrl || normalizedSmokeApiUrl !== smokeProfileApiUrl) { + throw new Error('Packaged desktop smoke profile API URL is invalid'); + } + const endpoint = `${normalizedSmokeApiUrl}/api/compatibility`; + const result = await window.webContents.executeJavaScript(`(async () => { + const response = await fetch(${JSON.stringify(endpoint)}, { credentials: 'include' }); + return { ok: response.ok, status: response.status, body: await response.json() }; + })()`); + if (result?.ok !== true || result?.body?.profileEndpoint !== true) { + throw new Error(`Packaged renderer profile API request failed with HTTP ${result?.status ?? 'unknown'}`); + } + log('info', 'desktop.renderer.profile_api.ready', { origin: DESKTOP_RENDERER_ORIGIN }); + } + log('info', 'desktop.renderer.ready', { preloadBridgeExposed: true }); + if (app.isPackaged && process.env.PROPR_DESKTOP_SMOKE_TEST === '1') { + app.quit(); + } else { + window.show(); + } + return window; +}; + +app.on('open-url', (event, url) => { + event.preventDefault(); + const normalized = normalizeDeepLink(url); + if (normalized) deliverDeepLink(normalized); +}); + +const hasSingleInstanceLock = app.requestSingleInstanceLock(); +if (!hasSingleInstanceLock) { + app.quit(); +} else { + app.on('second-instance', (_event, argv) => { + const deepLink = deepLinkFromArguments(argv); + if (deepLink) deliverDeepLink(deepLink); + if (mainWindow) { + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.show(); + mainWindow.focus(); + } + }); + + registerProtocolClient(); + void app.whenReady().then(async () => { + logger = createDesktopLogger(join(app.getPath('logs'), 'desktop.jsonl')); + log('info', 'desktop.app.ready', { version: app.getVersion(), platform: process.platform }); + configureSessionSecurity(); + configurePackagedRendererProtocol(); + + const encryption: EncryptionProvider = { + isEncryptionAvailable: () => safeStorage.isEncryptionAvailable(), + backend: () => { + if (process.platform !== 'linux') return 'os-protected'; + try { + return safeStorage.getSelectedStorageBackend(); + } catch { + return 'unavailable'; + } + }, + encrypt: value => safeStorage.encryptString(value), + decrypt: value => safeStorage.decryptString(value), + }; + const profiles = new ProfileStore(app.getPath('userData'), encryption); + const lifecycle = new LocalLifecycleController(); + registerIpcHandlers({ + app, + ipcMain, + profiles, + lifecycle, + logger, + desktopSession: session.defaultSession, + devServerUrl, + packagedRendererUrl, + }); + mainWindow = await createMainWindow(); + deepLinkDelivery.setWindow(mainWindow); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0) { + void createMainWindow().then(window => { + mainWindow = window; + deepLinkDelivery.setWindow(window); + }); + } + }); + + app.on('before-quit', event => { + if (shutdownStarted) return; + event.preventDefault(); + shutdownStarted = true; + void lifecycle.shutdown().finally(() => { + log('info', 'desktop.app.shutdown'); + app.quit(); + }); + }); + }).catch(error => { + log('error', 'desktop.app.start_failed', { error }); + app.exit(1); + }); +} + +app.on('window-all-closed', () => { + if (process.platform !== 'darwin') app.quit(); +}); diff --git a/apps/desktop/src/preload-bridge.test.ts b/apps/desktop/src/preload-bridge.test.ts new file mode 100644 index 000000000..81db36bef --- /dev/null +++ b/apps/desktop/src/preload-bridge.test.ts @@ -0,0 +1,79 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { createDesktopBridge, type PreloadIpc } from './preload-bridge'; +import { IPC_CHANNELS } from './shared/contract'; + +class FakeIpc implements PreloadIpc { + readonly invocations: Array<{ channel: string; args: unknown[] }> = []; + readonly listeners = new Map void>(); + + async invoke(channel: string, ...args: unknown[]): Promise { + this.invocations.push({ channel, args }); + return undefined; + } + + on(channel: string, listener: (event: unknown, value: string) => void): void { + this.listeners.set(channel, listener); + } + + removeListener(channel: string, listener: (event: unknown, value: string) => void): void { + if (this.listeners.get(channel) === listener) this.listeners.delete(channel); + } +} + +describe('desktop preload bridge', () => { + it('exposes only the narrow frozen namespaces', () => { + const bridge = createDesktopBridge(new FakeIpc()); + assert.deepEqual(Object.keys(bridge).sort(), ['app', 'auth', 'credentials', 'external', 'lifecycle', 'profiles', 'storage']); + assert.equal(Object.isFrozen(bridge), true); + assert.equal(Object.values(bridge).every(Object.isFrozen), true); + assert.equal('fs' in bridge, false); + assert.equal('exec' in bridge, false); + }); + + it('maps profile and credential operations to fixed channels', async () => { + const ipc = new FakeIpc(); + const bridge = createDesktopBridge(ipc); + await bridge.auth.logout('http://localhost:4000'); + await bridge.profiles.save({ label: 'Local', apiBaseUrl: 'http://localhost:4000' }); + await bridge.credentials.write('profile-1', 'secret'); + await bridge.lifecycle.start(); + assert.deepEqual(ipc.invocations, [ + { channel: IPC_CHANNELS.authLogout, args: ['http://localhost:4000'] }, + { + channel: IPC_CHANNELS.profilesSave, + args: [{ label: 'Local', apiBaseUrl: 'http://localhost:4000' }], + }, + { channel: IPC_CHANNELS.credentialsWrite, args: ['profile-1', 'secret'] }, + { channel: IPC_CHANNELS.lifecycleStart, args: [] }, + ]); + }); + + it('does not expose Electron event objects to deep-link listeners', () => { + const ipc = new FakeIpc(); + const bridge = createDesktopBridge(ipc); + const received: string[] = []; + const unsubscribe = bridge.app.onDeepLink(value => received.push(value)); + ipc.listeners.get(IPC_CHANNELS.deepLink)?.({ sender: 'must-not-leak' }, 'propr://open?path=%2Ftasks'); + assert.deepEqual(received, ['propr://open?path=%2Ftasks']); + unsubscribe(); + assert.equal(ipc.listeners.has(IPC_CHANNELS.deepLink), true); + }); + + it('buffers startup and second-instance deep links until the renderer subscribes', () => { + const ipc = new FakeIpc(); + const bridge = createDesktopBridge(ipc); + const receiveDeepLink = ipc.listeners.get(IPC_CHANNELS.deepLink); + assert.ok(receiveDeepLink, 'preload must register its IPC listener eagerly'); + + receiveDeepLink({}, 'propr://connect?api=http%3A%2F%2Flocalhost%3A4000'); + receiveDeepLink({}, 'propr://open?path=%2Ftasks'); + + const received: string[] = []; + bridge.app.onDeepLink(value => received.push(value)); + assert.deepEqual(received, [ + 'propr://connect?api=http%3A%2F%2Flocalhost%3A4000', + 'propr://open?path=%2Ftasks', + ]); + }); +}); diff --git a/apps/desktop/src/preload-bridge.ts b/apps/desktop/src/preload-bridge.ts new file mode 100644 index 000000000..3bba8300e --- /dev/null +++ b/apps/desktop/src/preload-bridge.ts @@ -0,0 +1,63 @@ +import type { DesktopBridge } from './shared/contract'; +import { IPC_CHANNELS } from './shared/contract'; + +export interface PreloadIpc { + invoke(channel: string, ...args: unknown[]): Promise; + on(channel: string, listener: (event: unknown, value: string) => void): void; + removeListener(channel: string, listener: (event: unknown, value: string) => void): void; +} + +const invoke = (ipc: PreloadIpc, channel: string, ...args: unknown[]): Promise => + ipc.invoke(channel, ...args) as Promise; + +export const createDesktopBridge = (ipc: PreloadIpc): DesktopBridge => { + const deepLinkListeners = new Set<(url: string) => void>(); + const pendingDeepLinks: string[] = []; + ipc.on(IPC_CHANNELS.deepLink, (_event, value) => { + if (deepLinkListeners.size === 0) { + pendingDeepLinks.push(value); + return; + } + deepLinkListeners.forEach(listener => listener(value)); + }); + + const bridge: DesktopBridge = { + app: { + getMetadata: () => invoke(ipc, IPC_CHANNELS.appMetadata), + onDeepLink: (listener) => { + deepLinkListeners.add(listener); + pendingDeepLinks.splice(0).forEach(value => listener(value)); + return () => deepLinkListeners.delete(listener); + }, + }, + auth: { + logout: (apiBaseUrl) => invoke(ipc, IPC_CHANNELS.authLogout, apiBaseUrl), + }, + external: { + open: (url) => invoke(ipc, IPC_CHANNELS.openExternal, url), + }, + storage: { + security: () => invoke(ipc, IPC_CHANNELS.storageSecurity), + }, + profiles: { + list: () => invoke(ipc, IPC_CHANNELS.profilesList), + save: (profile) => invoke(ipc, IPC_CHANNELS.profilesSave, profile), + remove: (profileId) => invoke(ipc, IPC_CHANNELS.profilesRemove, profileId), + setActive: (profileId) => invoke(ipc, IPC_CHANNELS.profilesSetActive, profileId), + }, + credentials: { + read: (profileId) => invoke(ipc, IPC_CHANNELS.credentialsRead, profileId), + write: (profileId, value) => invoke(ipc, IPC_CHANNELS.credentialsWrite, profileId, value), + remove: (profileId) => invoke(ipc, IPC_CHANNELS.credentialsRemove, profileId), + }, + lifecycle: { + status: () => invoke(ipc, IPC_CHANNELS.lifecycleStatus), + start: () => invoke(ipc, IPC_CHANNELS.lifecycleStart), + stop: () => invoke(ipc, IPC_CHANNELS.lifecycleStop), + restart: () => invoke(ipc, IPC_CHANNELS.lifecycleRestart), + }, + }; + + Object.values(bridge).forEach(Object.freeze); + return Object.freeze(bridge); +}; diff --git a/apps/desktop/src/preload.ts b/apps/desktop/src/preload.ts new file mode 100644 index 000000000..ba4f4d45b --- /dev/null +++ b/apps/desktop/src/preload.ts @@ -0,0 +1,4 @@ +import { contextBridge, ipcRenderer } from 'electron'; +import { createDesktopBridge } from './preload-bridge'; + +contextBridge.exposeInMainWorld('proprDesktop', createDesktopBridge(ipcRenderer)); diff --git a/apps/desktop/src/profile-store.test.ts b/apps/desktop/src/profile-store.test.ts new file mode 100644 index 000000000..c4807df05 --- /dev/null +++ b/apps/desktop/src/profile-store.test.ts @@ -0,0 +1,106 @@ +import assert from 'node:assert/strict'; +import { mkdtemp, readFile, rm } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { afterEach, describe, it } from 'node:test'; +import { ProfileStore, type EncryptionProvider } from './profile-store'; + +const temporaryDirectories: string[] = []; + +const createDirectory = async (): Promise => { + const directory = await mkdtemp(join(tmpdir(), 'propr-desktop-test-')); + temporaryDirectories.push(directory); + return directory; +}; + +const encryption = (available = true, backend = 'keychain'): EncryptionProvider => ({ + isEncryptionAvailable: () => available, + backend: () => backend, + encrypt: value => Buffer.from(Buffer.from(value, 'utf8').toString('base64url'), 'utf8'), + decrypt: value => Buffer.from(value.toString(), 'base64url').toString('utf8'), +}); + +afterEach(async () => { + await Promise.all(temporaryDirectories.splice(0).map(directory => rm(directory, { recursive: true, force: true }))); +}); + +describe('desktop profile store', () => { + it('persists validated profiles and active selection', async () => { + const directory = await createDirectory(); + const store = new ProfileStore(directory, encryption()); + const profile = await store.save({ label: ' Local ', apiBaseUrl: 'http://localhost:4000///' }); + const ipv6Profile = await store.save({ label: 'IPv6', apiBaseUrl: 'http://[::1]:4000/' }); + await store.setActive(profile.id); + assert.deepEqual(await store.list(), { profiles: [profile, ipv6Profile], activeProfileId: profile.id }); + assert.equal(profile.label, 'Local'); + assert.equal(profile.apiBaseUrl, 'http://localhost:4000'); + assert.equal(ipv6Profile.apiBaseUrl, 'http://[::1]:4000'); + }); + + it('encrypts credentials before writing app-owned storage', async () => { + const directory = await createDirectory(); + const store = new ProfileStore(directory, encryption()); + const profile = await store.save({ label: 'Secure', apiBaseUrl: 'https://propr.example.com' }); + assert.deepEqual(await store.writeCredential(profile.id, 'top-secret'), { stored: true }); + assert.deepEqual(await store.readCredential(profile.id), { available: true, value: 'top-secret' }); + const onDisk = await readFile(join(directory, 'desktop', 'credentials', `${profile.id}.bin`), 'utf8'); + assert.equal(onDisk, Buffer.from('top-secret', 'utf8').toString('base64url')); + assert.equal(onDisk.includes('top-secret'), false); + assert.notEqual(onDisk, 'top-secret'); + }); + + it('serializes concurrent credential writes with last-write semantics', async () => { + const store = new ProfileStore(await createDirectory(), encryption()); + + const first = store.writeCredential('profile-1', 'first'); + const second = store.writeCredential('profile-1', 'second'); + assert.deepEqual(await Promise.all([first, second]), [{ stored: true }, { stored: true }]); + assert.deepEqual(await store.readCredential('profile-1'), { available: true, value: 'second' }); + }); + + it('orders concurrent credential writes and removals by invocation', async () => { + const store = new ProfileStore(await createDirectory(), encryption()); + + await Promise.all([ + store.writeCredential('profile-1', 'remove-me'), + store.removeCredential('profile-1'), + ]); + assert.deepEqual(await store.readCredential('profile-1'), { available: true, value: null }); + + await Promise.all([ + store.removeCredential('profile-1'), + store.writeCredential('profile-1', 'keep-me'), + ]); + assert.deepEqual(await store.readCredential('profile-1'), { available: true, value: 'keep-me' }); + }); + + it('refuses plaintext fallback when encryption is unavailable or basic_text', async () => { + for (const provider of [encryption(false, 'unavailable'), encryption(true, 'basic_text')]) { + const directory = await createDirectory(); + const store = new ProfileStore(directory, provider); + assert.equal(store.security().available, false); + assert.deepEqual(await store.writeCredential('profile-1', 'secret'), { + stored: false, + reason: 'encryption-unavailable', + }); + assert.deepEqual(await store.readCredential('profile-1'), { available: false, value: null }); + } + }); + + it('rejects unsafe endpoints and path-like profile identifiers', async () => { + const directory = await createDirectory(); + const store = new ProfileStore(directory, encryption()); + const profile = await store.save({ label: 'Remote', apiBaseUrl: 'https://propr.example.com/' }); + await assert.rejects( + store.save({ label: 'Remote HTTP', apiBaseUrl: 'http://example.com' }), + /HTTPS/, + ); + await assert.rejects( + store.save({ id: profile.id, label: 'Path bearing', apiBaseUrl: 'https://propr.example.com/base' }), + /HTTPS/, + ); + assert.deepEqual((await store.list()).profiles, [profile]); + assert.doesNotMatch(await readFile(join(directory, 'desktop', 'profiles.json'), 'utf8'), /\/base/); + await assert.rejects(store.writeCredential('../escape', 'secret'), /Invalid desktop profile id/); + }); +}); diff --git a/apps/desktop/src/profile-store.ts b/apps/desktop/src/profile-store.ts new file mode 100644 index 000000000..4115c1f92 --- /dev/null +++ b/apps/desktop/src/profile-store.ts @@ -0,0 +1,250 @@ +import { randomUUID } from 'node:crypto'; +import { chmod, mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import type { + CredentialReadResult, + CredentialWriteResult, + DesktopProfile, + DesktopProfileInput, + DesktopProfileList, + StorageSecurity, +} from './shared/contract'; +import { normalizeApiBaseUrl } from './security'; + +const PROFILE_ID_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$/; +const MAX_CREDENTIAL_LENGTH = 65_536; + +interface PersistedState { + version: 1; + activeProfileId: string | null; + profiles: DesktopProfile[]; +} + +export interface EncryptionProvider { + isEncryptionAvailable(): boolean; + backend(): string; + encrypt(value: string): Buffer; + decrypt(value: Buffer): string; +} + +const emptyState = (): PersistedState => ({ + version: 1, + activeProfileId: null, + profiles: [], +}); + +const validDate = (value: unknown): value is string => + typeof value === 'string' && !Number.isNaN(Date.parse(value)); + +const validProfile = (value: unknown): value is DesktopProfile => { + if (!value || typeof value !== 'object') return false; + const profile = value as Record; + return typeof profile.id === 'string' + && PROFILE_ID_PATTERN.test(profile.id) + && typeof profile.label === 'string' + && profile.label.length > 0 + && profile.label.length <= 80 + && typeof profile.apiBaseUrl === 'string' + && normalizeApiBaseUrl(profile.apiBaseUrl) === profile.apiBaseUrl + && validDate(profile.createdAt) + && validDate(profile.updatedAt); +}; + +const parseState = (contents: string): PersistedState => { + const value = JSON.parse(contents) as unknown; + if (!value || typeof value !== 'object') throw new Error('Desktop profile store is invalid'); + const state = value as Record; + if (state.version !== 1 || !Array.isArray(state.profiles) || !state.profiles.every(validProfile)) { + throw new Error('Desktop profile store is invalid'); + } + if (state.activeProfileId !== null && ( + typeof state.activeProfileId !== 'string' + || !state.profiles.some((profile: DesktopProfile) => profile.id === state.activeProfileId) + )) { + throw new Error('Desktop active profile is invalid'); + } + return state as unknown as PersistedState; +}; + +const encryptionStatus = (encryption: EncryptionProvider): StorageSecurity => { + const backend = encryption.backend(); + if (!encryption.isEncryptionAvailable()) { + return { available: false, backend, reason: 'os-encryption-unavailable' }; + } + if (backend === 'basic_text') { + return { available: false, backend, reason: 'insecure-basic-text-backend' }; + } + return { available: true, backend }; +}; + +const assertProfileId: (profileId: unknown) => asserts profileId is string = (profileId) => { + if (typeof profileId !== 'string' || !PROFILE_ID_PATTERN.test(profileId)) { + throw new Error('Invalid desktop profile id'); + } +}; + +const normalizedProfileInput = (input: DesktopProfileInput): Omit => { + if (!input || typeof input !== 'object') throw new Error('Invalid desktop profile'); + const label = input.label?.trim(); + const apiBaseUrl = normalizeApiBaseUrl(input.apiBaseUrl ?? ''); + if (!label || label.length > 80) throw new Error('Profile label must contain 1 to 80 characters'); + if (!apiBaseUrl) throw new Error('Use HTTPS, or HTTP on localhost, for the ProPR API URL'); + const id = input.id ?? randomUUID(); + assertProfileId(id); + return { id, label, apiBaseUrl }; +}; + +export class ProfileStore { + readonly #directory: string; + readonly #statePath: string; + readonly #credentialsDirectory: string; + readonly #encryption: EncryptionProvider; + #mutation = Promise.resolve(); + readonly #credentialMutations = new Map>(); + + constructor(userDataPath: string, encryption: EncryptionProvider) { + this.#directory = join(userDataPath, 'desktop'); + this.#statePath = join(this.#directory, 'profiles.json'); + this.#credentialsDirectory = join(this.#directory, 'credentials'); + this.#encryption = encryption; + } + + security(): StorageSecurity { + return encryptionStatus(this.#encryption); + } + + async list(): Promise { + const state = await this.#readState(); + return { + profiles: state.profiles.map(profile => ({ ...profile })), + activeProfileId: state.activeProfileId, + }; + } + + save(input: DesktopProfileInput): Promise { + return this.#mutate(async () => { + const normalized = normalizedProfileInput(input); + const state = await this.#readState(); + const existing = state.profiles.find(profile => profile.id === normalized.id); + const now = new Date().toISOString(); + const profile: DesktopProfile = { + ...normalized, + createdAt: existing?.createdAt ?? now, + updatedAt: now, + }; + state.profiles = [...state.profiles.filter(item => item.id !== profile.id), profile]; + await this.#writeState(state); + return { ...profile }; + }); + } + + remove(profileId: string): Promise { + assertProfileId(profileId); + const stateMutation = this.#mutate(async () => { + const state = await this.#readState(); + state.profiles = state.profiles.filter(profile => profile.id !== profileId); + if (state.activeProfileId === profileId) state.activeProfileId = null; + await this.#writeState(state); + }); + return this.#mutateCredential(profileId, async () => { + await stateMutation; + await this.#removeCredentialFile(profileId); + }); + } + + setActive(profileId: string | null): Promise { + if (profileId !== null) assertProfileId(profileId); + return this.#mutate(async () => { + const state = await this.#readState(); + if (profileId !== null && !state.profiles.some(profile => profile.id === profileId)) { + throw new Error('Desktop profile does not exist'); + } + state.activeProfileId = profileId; + await this.#writeState(state); + }); + } + + async readCredential(profileId: string): Promise { + assertProfileId(profileId); + if (!this.security().available) return { available: false, value: null }; + try { + const encrypted = await readFile(this.#credentialPath(profileId)); + return { available: true, value: this.#encryption.decrypt(encrypted) }; + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { available: true, value: null }; + throw error; + } + } + + async writeCredential(profileId: string, value: string): Promise { + assertProfileId(profileId); + if (typeof value !== 'string' || value.length === 0 || value.length > MAX_CREDENTIAL_LENGTH) { + throw new Error('Credential must contain 1 to 65536 characters'); + } + if (!this.security().available) return { stored: false, reason: 'encryption-unavailable' }; + return this.#mutateCredential(profileId, async () => { + await this.#ensureDirectories(); + const target = this.#credentialPath(profileId); + const temporary = `${target}.${process.pid}.tmp`; + await writeFile(temporary, this.#encryption.encrypt(value), { mode: 0o600 }); + await rename(temporary, target); + await chmod(target, 0o600).catch(() => undefined); + return { stored: true }; + }); + } + + removeCredential(profileId: string): Promise { + assertProfileId(profileId); + return this.#mutateCredential(profileId, () => this.#removeCredentialFile(profileId)); + } + + async #removeCredentialFile(profileId: string): Promise { + await unlink(this.#credentialPath(profileId)).catch(error => { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; + }); + } + + async #readState(): Promise { + try { + return parseState(await readFile(this.#statePath, 'utf8')); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') return emptyState(); + throw error; + } + } + + async #writeState(state: PersistedState): Promise { + await this.#ensureDirectories(); + const temporary = `${this.#statePath}.${process.pid}.tmp`; + await writeFile(temporary, `${JSON.stringify(state, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 }); + await rename(temporary, this.#statePath); + await chmod(this.#statePath, 0o600).catch(() => undefined); + } + + async #ensureDirectories(): Promise { + await mkdir(this.#credentialsDirectory, { recursive: true, mode: 0o700 }); + await chmod(this.#directory, 0o700).catch(() => undefined); + await chmod(this.#credentialsDirectory, 0o700).catch(() => undefined); + } + + #credentialPath(profileId: string): string { + return join(this.#credentialsDirectory, `${profileId}.bin`); + } + + #mutate(operation: () => Promise): Promise { + const result = this.#mutation.then(operation, operation); + this.#mutation = result.then(() => undefined, () => undefined); + return result; + } + + #mutateCredential(profileId: string, operation: () => Promise): Promise { + const previous = this.#credentialMutations.get(profileId) ?? Promise.resolve(); + const result = previous.then(operation, operation); + const settled = result.then(() => undefined, () => undefined); + this.#credentialMutations.set(profileId, settled); + void settled.then(() => { + if (this.#credentialMutations.get(profileId) === settled) this.#credentialMutations.delete(profileId); + }); + return result; + } +} diff --git a/apps/desktop/src/security.test.ts b/apps/desktop/src/security.test.ts new file mode 100644 index 000000000..aecda058a --- /dev/null +++ b/apps/desktop/src/security.test.ts @@ -0,0 +1,96 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { + deepLinkFromArguments, + applyDevelopmentRendererCsp, + isSafeExternalUrl, + isTrustedRendererUrl, + normalizeApiBaseUrl, + normalizeDeepLink, + rendererContentSecurityPolicy, + validatedDevServerUrl, +} from './security'; + +describe('desktop URL security', () => { + it('only accepts HTTPS and loopback HTTP API endpoints', () => { + assert.equal(normalizeApiBaseUrl('https://propr.example.com///'), 'https://propr.example.com'); + assert.equal(normalizeApiBaseUrl('http://localhost:4000/'), 'http://localhost:4000'); + assert.equal(normalizeApiBaseUrl('http://127.0.0.1:4000'), 'http://127.0.0.1:4000'); + assert.equal(normalizeApiBaseUrl('http://[::1]:4000/'), 'http://[::1]:4000'); + assert.equal(normalizeApiBaseUrl('https://propr.example.com/base'), null); + assert.equal(normalizeApiBaseUrl('http://[::1]:4000/api'), null); + assert.equal(normalizeApiBaseUrl('http://propr.example.com'), null); + assert.equal(normalizeApiBaseUrl('http://[2001:db8::1]:4000'), null); + assert.equal(normalizeApiBaseUrl('https://user:secret@propr.example.com'), null); + assert.equal(normalizeApiBaseUrl('file:///tmp/propr'), null); + }); + + it('denies unsafe external browser schemes and credential-bearing URLs', () => { + assert.equal(isSafeExternalUrl('https://github.com/integry/propr'), true); + assert.equal(isSafeExternalUrl('http://localhost:4000/docs'), true); + assert.equal(isSafeExternalUrl('http://[::1]:4000/docs'), true); + assert.equal(isSafeExternalUrl('http://example.com'), false); + assert.equal(isSafeExternalUrl('http://[2001:db8::1]:4000/docs'), false); + assert.equal(isSafeExternalUrl('javascript:alert(1)'), false); + assert.equal(isSafeExternalUrl('file://[::1]/tmp/propr'), false); + assert.equal(isSafeExternalUrl('https://token@example.com'), false); + }); + + it('requires an exact loopback development origin', () => { + assert.equal(validatedDevServerUrl('http://localhost:5173/')?.origin, 'http://localhost:5173'); + assert.equal(validatedDevServerUrl('http://[::1]:5173/')?.origin, 'http://[::1]:5173'); + assert.equal(validatedDevServerUrl('https://localhost:5173/'), null); + assert.equal(validatedDevServerUrl('http://0.0.0.0:5173/'), null); + assert.equal(validatedDevServerUrl('http://[2001:db8::1]:5173/'), null); + assert.equal(validatedDevServerUrl('ws://[::1]:5173/'), null); + assert.equal(validatedDevServerUrl('http://localhost:5173/path'), null); + assert.equal( + isTrustedRendererUrl('http://localhost:5173/renderer.html', 'http://localhost:5173/', '/unused'), + true, + ); + assert.equal( + isTrustedRendererUrl('http://127.0.0.1:5173/renderer.html', 'http://localhost:5173/', '/unused'), + false, + ); + }); + + it('retains IPC trust for hash-routed packaged renderer URLs only', () => { + const renderer = 'propr-app://renderer/renderer.html'; + assert.equal(isTrustedRendererUrl(renderer, undefined, renderer), true); + assert.equal(isTrustedRendererUrl(`${renderer}#/plans/123`, undefined, renderer), true); + assert.equal(isTrustedRendererUrl(`${renderer}?profile=123#/plans/123`, undefined, renderer), false); + assert.equal(isTrustedRendererUrl('propr-app://renderer/other.html', undefined, renderer), false); + assert.equal(isTrustedRendererUrl('propr-app://other/renderer.html#/plans/123', undefined, renderer), false); + assert.equal(isTrustedRendererUrl('https://propr.example.com', undefined, renderer), false); + }); + + it('allowlists custom protocol actions and extracts them from argv', () => { + const link = 'propr://connect?api=https%3A%2F%2Fpropr.example.com'; + assert.equal(normalizeDeepLink(link), link); + assert.equal(deepLinkFromArguments(['electron', '.', link]), link); + assert.equal(normalizeDeepLink('propr://delete-everything'), null); + assert.equal(normalizeDeepLink('https://propr.example.com'), null); + assert.equal(normalizeDeepLink('propr://user:secret@connect'), null); + }); + + it('publishes a restrictive production policy', () => { + const policy = rendererContentSecurityPolicy(); + assert.match(policy, /default-src 'self'/); + assert.match(policy, /object-src 'none'/); + assert.match(policy, /frame-src 'none'/); + assert.doesNotMatch(policy, /unsafe-eval/); + assert.match(policy, /script-src 'self'(?:;|$)/); + assert.match(policy, /http:\/\/\[::1\]:\*/); + assert.match(policy, /ws:\/\/\[::1\]:\*/); + }); + + it('relaxes inline scripts only while Vite serves the development renderer', () => { + const packagedPolicy = rendererContentSecurityPolicy(); + const source = ``; + const transformed = applyDevelopmentRendererCsp(source); + + assert.match(transformed, /script-src 'self' 'unsafe-inline'/); + assert.equal(applyDevelopmentRendererCsp(source).includes(rendererContentSecurityPolicy(true)), true); + assert.match(packagedPolicy, /script-src 'self'(?:;|$)/); + }); +}); diff --git a/apps/desktop/src/security.ts b/apps/desktop/src/security.ts new file mode 100644 index 000000000..ab6ad6f73 --- /dev/null +++ b/apps/desktop/src/security.ts @@ -0,0 +1,92 @@ +import { DESKTOP_PROTOCOL } from './shared/contract'; + +// WHATWG URL.hostname retains brackets around IPv6 literals. +const LOOPBACK_HOSTS = new Set(['127.0.0.1', '[::1]', 'localhost']); +const DEEP_LINK_ACTIONS = new Set(['connect', 'open']); + +const parseUrl = (value: string): URL | null => { + try { + return new URL(value); + } catch { + return null; + } +}; + +const hasCredentials = (url: URL): boolean => Boolean(url.username || url.password); + +export const normalizeApiBaseUrl = (value: string): string | null => { + const url = parseUrl(value.trim()); + if (!url || hasCredentials(url) || url.hash || url.search) return null; + if (url.protocol === 'http:' && !LOOPBACK_HOSTS.has(url.hostname)) return null; + if (url.protocol !== 'http:' && url.protocol !== 'https:') return null; + if (url.pathname.replace(/\//g, '') !== '') return null; + return url.origin; +}; + +export const isSafeExternalUrl = (value: string): boolean => { + const url = parseUrl(value); + if (!url || hasCredentials(url)) return false; + return url.protocol === 'https:' + || (url.protocol === 'http:' && LOOPBACK_HOSTS.has(url.hostname)); +}; + +export const validatedDevServerUrl = (value: string | undefined): URL | null => { + if (!value) return null; + const url = parseUrl(value); + if (!url || url.protocol !== 'http:' || !LOOPBACK_HOSTS.has(url.hostname) || hasCredentials(url)) return null; + if (url.pathname !== '/' || url.search || url.hash) return null; + return url; +}; + +export const isTrustedRendererUrl = ( + candidate: string, + devServerUrl: string | undefined, + packagedRendererUrl: string, +): boolean => { + const candidateUrl = parseUrl(candidate); + if (!candidateUrl) return false; + const devUrl = validatedDevServerUrl(devServerUrl); + if (devUrl) return candidateUrl.origin === devUrl.origin; + const packagedUrl = parseUrl(packagedRendererUrl); + if (!packagedUrl || hasCredentials(candidateUrl) || candidateUrl.search) return false; + return candidateUrl.protocol === packagedUrl.protocol + && candidateUrl.host === packagedUrl.host + && candidateUrl.pathname === packagedUrl.pathname; +}; + +export const normalizeDeepLink = (value: string): string | null => { + if (value.length > 2_048) return null; + const url = parseUrl(value); + if (!url || url.protocol !== `${DESKTOP_PROTOCOL}:` || hasCredentials(url)) return null; + if (!DEEP_LINK_ACTIONS.has(url.hostname) || url.port || url.hash) return null; + return url.href; +}; + +export const deepLinkFromArguments = (argv: readonly string[]): string | null => { + for (const argument of argv) { + const normalized = normalizeDeepLink(argument); + if (normalized) return normalized; + } + return null; +}; + +export const rendererContentSecurityPolicy = (development = false): string => [ + "default-src 'self'", + `script-src 'self'${development ? " 'unsafe-inline'" : ''}`, + "style-src 'self' 'unsafe-inline'", + "img-src 'self' data: blob: https:", + "font-src 'self' data:", + "connect-src 'self' https: http://127.0.0.1:* http://[::1]:* http://localhost:* ws://127.0.0.1:* ws://[::1]:* ws://localhost:* wss:", + "object-src 'none'", + "base-uri 'none'", + "form-action 'none'", + "frame-src 'none'", +].join('; '); + +export const applyDevelopmentRendererCsp = (html: string): string => { + const packagedPolicy = rendererContentSecurityPolicy(); + if (!html.includes(packagedPolicy)) { + throw new Error('renderer.html is missing the packaged content security policy'); + } + return html.replace(packagedPolicy, rendererContentSecurityPolicy(true)); +}; diff --git a/apps/desktop/src/shared/contract.ts b/apps/desktop/src/shared/contract.ts new file mode 100644 index 000000000..f34d23298 --- /dev/null +++ b/apps/desktop/src/shared/contract.ts @@ -0,0 +1,111 @@ +export const DESKTOP_PROTOCOL = 'propr'; + +export const IPC_CHANNELS = Object.freeze({ + appMetadata: 'desktop:app-metadata', + authLogout: 'desktop:auth-logout', + openExternal: 'desktop:open-external', + storageSecurity: 'desktop:storage-security', + profilesList: 'desktop:profiles-list', + profilesSave: 'desktop:profiles-save', + profilesRemove: 'desktop:profiles-remove', + profilesSetActive: 'desktop:profiles-set-active', + credentialsRead: 'desktop:credentials-read', + credentialsWrite: 'desktop:credentials-write', + credentialsRemove: 'desktop:credentials-remove', + lifecycleStatus: 'desktop:lifecycle-status', + lifecycleStart: 'desktop:lifecycle-start', + lifecycleStop: 'desktop:lifecycle-stop', + lifecycleRestart: 'desktop:lifecycle-restart', + deepLink: 'desktop:deep-link', +} as const); + +export type DesktopPlatform = 'aix' | 'android' | 'darwin' | 'freebsd' | 'haiku' + | 'linux' | 'openbsd' | 'sunos' | 'win32' | 'cygwin' | 'netbsd'; + +export interface DesktopAppMetadata { + name: string; + version: string; + platform: DesktopPlatform; + arch: string; + packaged: boolean; +} + +export interface DesktopProfile { + id: string; + label: string; + apiBaseUrl: string; + createdAt: string; + updatedAt: string; +} + +export interface DesktopProfileInput { + id?: string; + label: string; + apiBaseUrl: string; +} + +export interface DesktopProfileList { + profiles: DesktopProfile[]; + activeProfileId: string | null; +} + +export type StorageSecurity = { + available: true; + backend: string; +} | { + available: false; + backend: string; + reason: 'os-encryption-unavailable' | 'insecure-basic-text-backend'; +}; + +export type CredentialReadResult = + | { available: false; value: null } + | { available: true; value: string | null }; + +export type CredentialWriteResult = + | { stored: true } + | { stored: false; reason: 'encryption-unavailable' }; + +export type LocalLifecycleState = 'disconnected' | 'starting' | 'connected' | 'stopping' | 'error'; + +export interface LocalLifecycleStatus { + state: LocalLifecycleState; + detail?: string; +} + +export type LocalLifecycleOperationResult = + | { ok: true; status: LocalLifecycleStatus } + | { ok: false; code: 'not-implemented'; status: LocalLifecycleStatus }; + +export interface DesktopBridge { + app: { + getMetadata(): Promise; + onDeepLink(listener: (url: string) => void): () => void; + }; + auth: { + logout(apiBaseUrl: string): Promise; + }; + external: { + open(url: string): Promise; + }; + storage: { + security(): Promise; + }; + profiles: { + list(): Promise; + save(profile: DesktopProfileInput): Promise; + remove(profileId: string): Promise; + setActive(profileId: string | null): Promise; + }; + credentials: { + read(profileId: string): Promise; + write(profileId: string, value: string): Promise; + remove(profileId: string): Promise; + }; + lifecycle: { + status(): Promise; + start(): Promise; + stop(): Promise; + restart(): Promise; + }; +} diff --git a/apps/desktop/src/vite-file-system-url.test.ts b/apps/desktop/src/vite-file-system-url.test.ts new file mode 100644 index 000000000..9d2bddc75 --- /dev/null +++ b/apps/desktop/src/vite-file-system-url.test.ts @@ -0,0 +1,19 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { viteFileSystemUrl } from './vite-file-system-url'; + +describe('Vite filesystem renderer URLs', () => { + it('preserves an absolute POSIX path after the /@fs/ prefix', () => { + assert.equal( + viteFileSystemUrl('/home/propr/propr-ui/src/desktop.tsx'), + '/@fs/home/propr/propr-ui/src/desktop.tsx', + ); + }); + + it('normalizes a Windows drive-letter path and separators', () => { + assert.equal( + viteFileSystemUrl('C:\\propr\\propr-ui\\src\\desktop.tsx'), + '/@fs/C:/propr/propr-ui/src/desktop.tsx', + ); + }); +}); diff --git a/apps/desktop/src/vite-file-system-url.ts b/apps/desktop/src/vite-file-system-url.ts new file mode 100644 index 000000000..4d6b1fed0 --- /dev/null +++ b/apps/desktop/src/vite-file-system-url.ts @@ -0,0 +1,5 @@ +/** Convert an absolute native path into Vite's cross-platform /@fs/ URL form. */ +export const viteFileSystemUrl = (absolutePath: string): string => { + const normalizedPath = absolutePath.replace(/\\/g, '/').replace(/^\/+/, ''); + return `/@fs/${normalizedPath}`; +}; diff --git a/apps/desktop/src/window-options.test.ts b/apps/desktop/src/window-options.test.ts new file mode 100644 index 000000000..240c66740 --- /dev/null +++ b/apps/desktop/src/window-options.test.ts @@ -0,0 +1,25 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { createBrowserWindowOptions } from './window-options'; + +describe('desktop BrowserWindow security', () => { + it('isolates and sandboxes the renderer without Node or webviews', () => { + const options = createBrowserWindowOptions('/app/preload.cjs', true, 'linux'); + assert.deepEqual(options.webPreferences, { + preload: '/app/preload.cjs', + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + webSecurity: true, + allowRunningInsecureContent: false, + webviewTag: false, + devTools: true, + }); + assert.equal('enableRemoteModule' in (options.webPreferences ?? {}), false); + }); + + it('uses the native inset title bar only on macOS', () => { + assert.equal(createBrowserWindowOptions('/preload.cjs', false, 'darwin').titleBarStyle, 'hiddenInset'); + assert.equal(createBrowserWindowOptions('/preload.cjs', false, 'win32').titleBarStyle, undefined); + }); +}); diff --git a/apps/desktop/src/window-options.ts b/apps/desktop/src/window-options.ts new file mode 100644 index 000000000..797f9d3be --- /dev/null +++ b/apps/desktop/src/window-options.ts @@ -0,0 +1,26 @@ +import type { BrowserWindowConstructorOptions } from 'electron'; + +export const createBrowserWindowOptions = ( + preloadPath: string, + allowDevTools: boolean, + platform: NodeJS.Platform = process.platform, +): BrowserWindowConstructorOptions => ({ + title: 'ProPR Desktop', + width: 1280, + height: 820, + minWidth: 880, + minHeight: 620, + backgroundColor: '#f8fafc', + show: false, + ...(platform === 'darwin' ? { titleBarStyle: 'hiddenInset' as const } : {}), + webPreferences: { + preload: preloadPath, + contextIsolation: true, + nodeIntegration: false, + sandbox: true, + webSecurity: true, + allowRunningInsecureContent: false, + webviewTag: false, + devTools: allowDevTools, + }, +}); diff --git a/apps/desktop/tsconfig.json b/apps/desktop/tsconfig.json new file mode 100644 index 000000000..1cd5d0235 --- /dev/null +++ b/apps/desktop/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "lib": ["ES2022", "DOM"], + "types": ["node"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "jsx": "react-jsx" + }, + "include": [ + "src/**/*.ts", + "src/**/*.tsx", + "forge.config.ts", + "vite.*.config.ts" + ] +} diff --git a/apps/desktop/vite.main.config.ts b/apps/desktop/vite.main.config.ts new file mode 100644 index 000000000..3fac6a497 --- /dev/null +++ b/apps/desktop/vite.main.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + sourcemap: true, + minify: false, + rollupOptions: { + output: { + format: 'cjs', + entryFileNames: 'main.cjs', + }, + }, + }, +}); diff --git a/apps/desktop/vite.preload.config.ts b/apps/desktop/vite.preload.config.ts new file mode 100644 index 000000000..d5353c7db --- /dev/null +++ b/apps/desktop/vite.preload.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + build: { + sourcemap: true, + minify: false, + rollupOptions: { + output: { + format: 'cjs', + entryFileNames: 'preload.cjs', + }, + }, + }, +}); diff --git a/apps/desktop/vite.renderer.config.ts b/apps/desktop/vite.renderer.config.ts new file mode 100644 index 000000000..c8de93b75 --- /dev/null +++ b/apps/desktop/vite.renderer.config.ts @@ -0,0 +1,54 @@ +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import react from '@vitejs/plugin-react'; +import { defineConfig, type Plugin } from 'vite'; +import { applyDevelopmentRendererCsp } from './src/security'; +import { viteFileSystemUrl } from './src/vite-file-system-url'; + +const rootPackage = JSON.parse( + readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'), +) as { version: string }; +const rendererEntrySource = '../../propr-ui/src/desktop.tsx'; +const rendererEntryDevelopmentUrl = viteFileSystemUrl( + fileURLToPath(new URL(rendererEntrySource, import.meta.url)), +); + +const transformDevelopmentRendererHtml = (html: string): string => { + if (!html.includes(rendererEntrySource)) { + throw new Error('renderer.html is missing the shared desktop renderer entry'); + } + return applyDevelopmentRendererCsp(html).replace(rendererEntrySource, rendererEntryDevelopmentUrl); +}; + +const developmentCspPlugin: Plugin = { + name: 'propr-desktop-development-csp', + apply: 'serve', + transformIndexHtml: { + order: 'pre', + handler: transformDevelopmentRendererHtml, + }, +}; + +export default defineConfig({ + base: './', + define: { + __APP_VERSION__: JSON.stringify(rootPackage.version), + __PROPR_DESKTOP__: 'true', + }, + plugins: [developmentCspPlugin, react()], + publicDir: '../../propr-ui/public', + build: { + sourcemap: true, + rollupOptions: { + input: 'renderer.html', + output: { + manualChunks: { + 'charts-vendor': ['recharts'], + 'markdown-vendor': ['react-markdown', 'remark-breaks', 'remark-gfm'], + 'motion-vendor': ['framer-motion'], + 'react-vendor': ['react', 'react-dom', 'react-router-dom'], + }, + }, + }, + }, +}); diff --git a/package-lock.json b/package-lock.json index b160a739f..88956cb9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -71,540 +71,1148 @@ "node": ">=22.12.0" } }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" + "apps/desktop": { + "name": "@propr/desktop", + "version": "0.8.15", + "license": "Apache-2.0", + "devDependencies": { + "@electron-forge/cli": "8.0.0-alpha.10", + "@electron-forge/maker-deb": "8.0.0-alpha.10", + "@electron-forge/maker-rpm": "8.0.0-alpha.10", + "@electron-forge/maker-squirrel": "8.0.0-alpha.10", + "@electron-forge/maker-zip": "8.0.0-alpha.10", + "@electron-forge/plugin-vite": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10", + "@electron/fuses": "^2.1.3", + "@types/node": "^22.10.0", + "@vitejs/plugin-react": "^4.6.0", + "electron": "^44.0.0", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite": "^7.3.5" + } }, - "node_modules/@alcalzone/ansi-tokenize": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", - "integrity": "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==", + "apps/desktop/node_modules/@electron-forge/cli": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/cli/-/cli-8.0.0-alpha.10.tgz", + "integrity": "sha512-3fkKH50xTVN1A+UhsX6BzwFfP7JVTadIrA3Cs4jpR7Yl/PChH4w/cyi99LNnZnVAypiywkOkqrQU9t+1SZy1YA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.electron-forge-cli?utm_medium=referral&utm_source=npm_fund" + } + ], "license": "MIT", "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" + "@electron-forge/core": "8.0.0-alpha.10", + "@electron-forge/core-utils": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10", + "@electron/get": "^5.0.0", + "commander": "^11.1.0", + "debug": "^4.3.1", + "listr2": "^7.0.2", + "semver": "^7.2.1" + }, + "bin": { + "electron-forge": "dist/electron-forge.js", + "electron-forge-vscode-nix": "script/vscode.sh", + "electron-forge-vscode-win": "script/vscode.cmd" }, "engines": { - "node": ">=18" + "node": ">= 22.12.0" } }, - "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "apps/desktop/node_modules/@electron-forge/core": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/core/-/core-8.0.0-alpha.10.tgz", + "integrity": "sha512-sg52Ay0vy9ShC7G4CL9fsfzcUC4yAI9HdP7D18tdmbPwZJ6DLqDLKT/pFw297V7IjX4AYlpsW/71yPEqadDm3w==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.electron-forge-core?utm_medium=referral&utm_source=npm_fund" + } + ], "license": "MIT", - "engines": { - "node": ">=12" + "dependencies": { + "@electron-forge/core-utils": "8.0.0-alpha.10", + "@electron-forge/maker-base": "8.0.0-alpha.10", + "@electron-forge/plugin-base": "8.0.0-alpha.10", + "@electron-forge/publisher-base": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10", + "@electron-forge/tracer": "8.0.0-alpha.10", + "@electron/get": "^5.0.0", + "@electron/packager": "^20.0.1", + "debug": "^4.3.1", + "graceful-fs": "^4.2.11", + "jiti": "^2.4.2", + "listr2": "^7.0.2" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">= 22.12.0" } }, - "node_modules/@alloc/quick-lru": { - "version": "5.2.0", + "apps/desktop/node_modules/@electron-forge/core-utils": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/core-utils/-/core-utils-8.0.0-alpha.10.tgz", + "integrity": "sha512-edL4xReqbWStPhdhgSEE55AXXLtJLxMRtHEghulmZlf4UaSfS86zwSBtqDwYcUB1cd9LpcEm3GKKek/awOJB0A==", "dev": true, "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "8.0.0-alpha.10", + "@electron/rebuild": "^4.0.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "graceful-fs": "^4.2.11", + "semver": "^7.2.1" + }, "engines": { - "node": ">=10" + "node": ">= 22.12.0" + } + }, + "apps/desktop/node_modules/@electron-forge/maker-base": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-base/-/maker-base-8.0.0-alpha.10.tgz", + "integrity": "sha512-aZ7YlU785r/1VPy0h1HHy1VEiufqMX0fd4tzHcAWwDfZguajfhnGioPfgCaEVKBWyAgV3v7Pge2FkL7YcRsxsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "8.0.0-alpha.10", + "which": "^6.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 22.12.0" } }, - "node_modules/@anthropic-ai/claude-code": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.220.tgz", - "integrity": "sha512-ogBrvwkqF9f8okmnXKxmRNHuvtFxFEffe5pWdqOV3iQDxlUOKirFqnyWC7NGXXnDA4WkkbPH8pvSbwyCR2Auyw==", - "hasInstallScript": true, - "license": "SEE LICENSE IN README.md", - "bin": { - "claude": "bin/claude.exe" + "apps/desktop/node_modules/@electron-forge/maker-deb": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-8.0.0-alpha.10.tgz", + "integrity": "sha512-0uk9bCW+UsPSyIASvCRzhUJii0WRCWo2oQKGZGFelIEdfPo8ojriM2ip2zVQP21c2Q0sSiaky+Ehizsymtcd6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-forge/maker-base": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10" }, "engines": { - "node": ">=22.0.0" + "node": ">= 22.12.0" }, "optionalDependencies": { - "@anthropic-ai/claude-code-darwin-arm64": "2.1.220", - "@anthropic-ai/claude-code-darwin-x64": "2.1.220", - "@anthropic-ai/claude-code-linux-arm64": "2.1.220", - "@anthropic-ai/claude-code-linux-arm64-musl": "2.1.220", - "@anthropic-ai/claude-code-linux-x64": "2.1.220", - "@anthropic-ai/claude-code-linux-x64-musl": "2.1.220", - "@anthropic-ai/claude-code-win32-arm64": "2.1.220", - "@anthropic-ai/claude-code-win32-x64": "2.1.220" + "electron-installer-debian": "^3.2.0" } }, - "node_modules/@anthropic-ai/claude-code-darwin-arm64": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-arm64/-/claude-code-darwin-arm64-2.1.220.tgz", - "integrity": "sha512-rmtd41Bf+n+YnhjSjtQ8WG5qy8KKogUp3YRfQrkLsTgPUD0H3j869rBInBJT3SHrKQ0hLghQLGM73CC1C+USLQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@anthropic-ai/claude-code-darwin-x64": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-x64/-/claude-code-darwin-x64-2.1.220.tgz", - "integrity": "sha512-hbuoG+YCo37VzSKzKJ47ymRmt/YjASc3dRcsZtCcftLYdopv8KL889x/IbCl3cfp/VqV2rRDZ0f3aUDpHUFweQ==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@anthropic-ai/claude-code-linux-arm64": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64/-/claude-code-linux-arm64-2.1.220.tgz", - "integrity": "sha512-VHFI8mKruIntKn7eq81sbyS19/KWmQcmJQsS/C+j9M/E+w0s4UytgsL7DADPjBE/GByNiKoRtLYDMntCjRlOdA==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-code-linux-arm64-musl": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64-musl/-/claude-code-linux-arm64-musl-2.1.220.tgz", - "integrity": "sha512-m37ALw8jcbSknuyG7xDQjGPY7Gth3eX8iFY1XFEWABVq1iUMVAUn96WC9eqwi8/JSqyG2t3oNRiqHdi2ZNKFGQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-code-linux-x64": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64/-/claude-code-linux-x64-2.1.220.tgz", - "integrity": "sha512-3CGFCnI0gpgsqNeJruFALBDGJaKXOuok3alQEg56ty2yOPpIrOx/r2Y0+T4uhJl7kP5Hzw4IFkxo4DZKWvzQ7Q==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-code-linux-x64-musl": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64-musl/-/claude-code-linux-x64-musl-2.1.220.tgz", - "integrity": "sha512-+QyT1KikOdMRKReWFaBYGsroYx2vEjjx54DwhMoC24oE1DxjC+SlKjeOTRXAKiu0fr0O549Lkhg2tuT5xtQpAQ==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@anthropic-ai/claude-code-win32-arm64": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-arm64/-/claude-code-win32-arm64-2.1.220.tgz", - "integrity": "sha512-APqZwFBn38DBUwB65uUTetW7lbtUqFfAfOWKvkmOyqFDswDEsInaINuIwqMCl44WYcch10SaHhEZdXJU9MG3aQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@anthropic-ai/claude-code-win32-x64": { - "version": "2.1.220", - "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-x64/-/claude-code-win32-x64-2.1.220.tgz", - "integrity": "sha512-UGrjH8cGhC6PzhTyZSdgf/RpKxpfk9XJZ/RT/wsG2AJg9yEJLjLg6/TrnlL8RFbEv6Zahu0Quytc02UOpA/GiA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@anthropic-ai/sdk": { - "version": "0.71.2", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", - "integrity": "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==", + "apps/desktop/node_modules/@electron-forge/maker-rpm": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-rpm/-/maker-rpm-8.0.0-alpha.10.tgz", + "integrity": "sha512-jtKz2D2WM/8l8q3difzNdrRCK8oDm1xUXfRmP9et0a31imyLRoalSR/STDjHQ4HiXfWnDZzuw0BvekzVjgGlRw==", + "dev": true, "license": "MIT", "dependencies": { - "json-schema-to-ts": "^3.1.1" - }, - "bin": { - "anthropic-ai-sdk": "bin/cli" + "@electron-forge/maker-base": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10" }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" + "engines": { + "node": ">= 22.12.0" }, - "peerDependenciesMeta": { - "zod": { - "optional": true - } + "optionalDependencies": { + "electron-installer-redhat": "^3.2.0" } }, - "node_modules/@asamuzakjp/css-color": { - "version": "5.1.10", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz", - "integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==", + "apps/desktop/node_modules/@electron-forge/maker-squirrel": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-squirrel/-/maker-squirrel-8.0.0-alpha.10.tgz", + "integrity": "sha512-AFCeuAgUWyr4G61hIXLr0pLZDNV4hvd8IgBXkfrWToMp09esE9jXS9o0SFpU40iETFCst2KxhqhraDt6URAj9Q==", "dev": true, "license": "MIT", "dependencies": { - "@csstools/css-calc": "^3.1.1", - "@csstools/css-color-parser": "^4.0.2", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" + "@electron-forge/core-utils": "8.0.0-alpha.10", + "@electron-forge/maker-base": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">= 22.12.0" + }, + "optionalDependencies": { + "electron-winstaller": "^5.3.0" } }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", - "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", + "apps/desktop/node_modules/@electron-forge/maker-zip": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/maker-zip/-/maker-zip-8.0.0-alpha.10.tgz", + "integrity": "sha512-I3N9FI8xJW7f+Ld05f2hSSuukfI2Oh9vKN7HWSPmM8+7PqN4Dwigp7DRv/s3HPFwrMdayDJKm/2me5rvXh32DQ==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" + "@electron-forge/core-utils": "8.0.0-alpha.10", + "@electron-forge/maker-base": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10", + "cross-zip": "^4.0.0" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">= 22.12.0" } }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "apps/desktop/node_modules/@electron-forge/plugin-base": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/plugin-base/-/plugin-base-8.0.0-alpha.10.tgz", + "integrity": "sha512-AoL+VuuFVLgqeRzO0dLvrx4f2t1nMeHQ1YKj/EoqAQ6uU7D4HS2D4FNEXyxTQFVrNj4OqSte7U3sqGenc327XA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "8.0.0-alpha.10" + }, + "engines": { + "node": ">= 22.12.0" + } }, - "node_modules/@babel/code-frame": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "apps/desktop/node_modules/@electron-forge/plugin-vite": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/plugin-vite/-/plugin-vite-8.0.0-alpha.10.tgz", + "integrity": "sha512-ctt+M1D1K5Or07oGWGUByLHfPJW91Qn1JKHWhJEkEZmrp6ggJrSrp7JqgBhNAqe5XtpEhhPCtDMaRfFmcSL+2g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" + "@electron-forge/core-utils": "8.0.0-alpha.10", + "@electron-forge/plugin-base": "8.0.0-alpha.10", + "@electron-forge/shared-types": "8.0.0-alpha.10", + "debug": "^4.3.1", + "listr2": "^7.0.2" }, "engines": { - "node": ">=6.9.0" + "node": ">= 22.12.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "apps/desktop/node_modules/@electron-forge/publisher-base": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/publisher-base/-/publisher-base-8.0.0-alpha.10.tgz", + "integrity": "sha512-UjGRM13jVr1oq+HLayJAUiQcfxvs8LyTQYm5sazxlfG9LO9UJAI/2jbNic/OXYehr5xGrJSCMXDTm/cCy5LfZQ==", "dev": true, "license": "MIT", + "dependencies": { + "@electron-forge/shared-types": "8.0.0-alpha.10" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 22.12.0" } }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "apps/desktop/node_modules/@electron-forge/shared-types": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/shared-types/-/shared-types-8.0.0-alpha.10.tgz", + "integrity": "sha512-JdwOXHXXjh1L1rgLcQJfyCX8cHgvognmuol/udDUIx9/JzMc+AZhNnsFN8JriRYunYaFrVLTHe0H8f8GQXO/LA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" + "@electron-forge/tracer": "8.0.0-alpha.10", + "@electron/packager": "^20.0.1", + "@electron/rebuild": "^4.0.1", + "listr2": "^7.0.2" }, "engines": { - "node": ">=6.9.0" + "node": ">= 22.12.0" + } + }, + "apps/desktop/node_modules/@electron-forge/tracer": { + "version": "8.0.0-alpha.10", + "resolved": "https://registry.npmjs.org/@electron-forge/tracer/-/tracer-8.0.0-alpha.10.tgz", + "integrity": "sha512-aoW9P+KoTtO0KQaISdJXi3sVB5k12P1kA6pK0NsgJTEbsB2i5O6c8zfor/U5eJUeb9GAVscAIHKShYuUycgZqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chrome-trace-event": "^1.0.3" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "engines": { + "node": ">= 22.12.0" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", + "apps/desktop/node_modules/@electron/asar": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-4.3.0.tgz", + "integrity": "sha512-k/FFC/NQoTykGBi/Ga4L4KorsAKDdkK0LfNVG90eUG6vPVpJaL3iR9V1ZLbzBAF3QpojK5DcaGOdQOheKZv4JQ==", "dev": true, - "license": "ISC", + "license": "MIT", + "dependencies": { + "glob": "^13.0.2", + "minimatch": "^10.0.1" + }, "bin": { - "semver": "bin/semver.js" + "asar": "bin/asar.mjs" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/generator": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", - "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "apps/desktop/node_modules/@electron/fuses": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-2.1.3.tgz", + "integrity": "sha512-LoKJUXNiJ4JM8IIrUltSHI+8pkogaGj5wmJx81jE/Wk3g2w1/kfMbTEKNoY5kitGE8hiC12h32R/1SlywFtxXg==", + "dev": true, + "license": "MIT", + "bin": { + "electron-fuses": "dist/bin.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "apps/desktop/node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.8", - "@babel/types": "^7.29.8", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "apps/desktop/node_modules/@electron/notarize": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-3.1.1.tgz", + "integrity": "sha512-uQQSlOiJnqRkTL1wlEBAxe90nVN/Fc/hEmk0bqpKk8nKjV1if/tXLHKUPePtv9Xsx90PtZU8aidx5lAiOpjkQQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" + "debug": "^4.4.0", + "promise-retry": "^2.0.1" }, "engines": { - "node": ">=6.9.0" + "node": ">= 22.12.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "apps/desktop/node_modules/@electron/osx-sign": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-2.7.0.tgz", + "integrity": "sha512-9DGhNqKMl6ibkhUoXbN7OHX2gZznfY10L3ZwG0u6r667Kfb6kec4JEfFTXftoqzmOfZ+OzwDbr4p/nKBMHnz0g==", "dev": true, - "license": "ISC", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.3.4", + "isbinaryfile": "^4.0.8", + "plist": "^3.0.5", + "semver": "^7.7.1" + }, "bin": { - "semver": "bin/semver.js" + "electron-osx-flat": "bin/electron-osx-flat.mjs", + "electron-osx-sign": "bin/electron-osx-sign.mjs" + }, + "engines": { + "node": ">=22.12.0" } }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "apps/desktop/node_modules/@electron/packager": { + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/@electron/packager/-/packager-20.3.0.tgz", + "integrity": "sha512-3MvgJgy6YJ5ti0oGGBKrWKdYwpTaoRrhranMDgHSQ6i5t56yZV9IRDyoXTuqBp97LiKqnZeAMe2wTcF/9+fP5g==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/asar": "^4.0.1", + "@electron/get": "^5.0.0", + "@electron/notarize": "^3.1.0", + "@electron/osx-sign": "^2.2.0", + "@electron/universal": "^3.0.1", + "@electron/windows-sign": "^2.0.2", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.4.1", + "filenamify": "^6.0.0", + "galactus": "^2.0.2", + "graceful-fs": "^4.2.11", + "junk": "^4.0.1", + "plist": "^3.1.0", + "resedit": "^2.0.3", + "semver": "^7.7.2", + "yargs-parser": "^22.0.0" + }, + "bin": { + "electron-packager": "bin/electron-packager.mjs" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 22.12.0" + }, + "funding": { + "url": "https://github.com/electron/packager?sponsor=1" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "apps/desktop/node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" }, "engines": { - "node": ">=6.9.0" + "node": ">=22.12.0" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "apps/desktop/node_modules/@electron/universal": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-3.0.6.tgz", + "integrity": "sha512-MonS1kfkZdSEkLZI0pdR/TCx8ecxwRSFm7sORfwIkDI9UaIbHnk4Mgeqq+Ob9qDQRV8LZ9+hHCmimpA9BRcNxw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@electron/asar": "^4.0.0", + "debug": "^4.3.1", + "plist": "^3.1.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=22.12.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", + "apps/desktop/node_modules/@electron/windows-sign": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-2.0.6.tgz", + "integrity": "sha512-ESWgNkFsXFH06I5EB2uHv3hmZA3yF4j9GTB77W74x3b5KZNItxggNzuADw41HUy6mhnC2K+E2mT0Xgc4YKu3IQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.3.4", + "graceful-fs": "^4.2.11", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.mjs" + }, "engines": { - "node": ">=6.9.0" + "node": ">=22.12.0" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "apps/desktop/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">=16" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "apps/desktop/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "apps/desktop/node_modules/filename-reserved-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "apps/desktop/node_modules/filenamify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "filename-reserved-regex": "^3.0.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/parser": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", - "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "apps/desktop/node_modules/flora-colossus": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-3.0.2.tgz", + "integrity": "sha512-Jk78K/Tzt6saxQPGChlJw69xuFGpWyTSAS8EdU0h/FyXwD2K46yNOXmo6nRHcZ9ooekyBAzMkwmiGNt7wOC5zg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.8" - }, - "bin": { - "parser": "bin/babel-parser.js" + "debug": "^4.4.1" }, "engines": { - "node": ">=6.0.0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", + "apps/desktop/node_modules/galactus": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/galactus/-/galactus-2.0.2.tgz", + "integrity": "sha512-HmKyTFGomdAchz4umx8MwBnrnfFmdpwiTyGA4ZOF7rya2Lmgbc9qate4yweInL+0gUBVImhaz12SBGpW3SY4Yg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "debug": "^4.4.1", + "flora-colossus": "^3.0.2" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=22.12.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", + "apps/desktop/node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": ">=6.9.0" + "node": "18 || 20 || >=22" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@babel/runtime": { - "version": "7.28.4", + "apps/desktop/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "apps/desktop/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, + "license": "BlueOak-1.0.0", "engines": { - "node": ">=6.9.0" + "node": ">=20" } }, - "node_modules/@babel/traverse": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", - "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "apps/desktop/node_modules/junk": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz", + "integrity": "sha512-Qush0uP+G8ZScpGMZvHUiRfI0YBWuB3gVBYlI0v0vvOJt5FLicco+IkP0a50LqTTQhmts/m6tP5SWE+USyIvcQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.8", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.8", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.8", - "debug": "^4.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/types": { - "version": "7.29.8", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", - "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "apps/desktop/node_modules/node-abi": { + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" + "semver": "^7.6.3" }, "engines": { - "node": ">=6.9.0" + "node": ">=22.12.0" } }, - "node_modules/@bramus/specificity": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", - "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "apps/desktop/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "apps/desktop/node_modules/yargs-parser": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", + "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=23" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alcalzone/ansi-tokenize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.3.0.tgz", + "integrity": "sha512-p+CMKJ93HFmLkjXKlXiVGlMQEuRb6H0MokBSwUsX+S6BRX8eV5naFZpQJFfJHjRZY0Hmnqy1/r6UWl3x+19zYA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@alcalzone/ansi-tokenize/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@anthropic-ai/claude-code": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.220.tgz", + "integrity": "sha512-ogBrvwkqF9f8okmnXKxmRNHuvtFxFEffe5pWdqOV3iQDxlUOKirFqnyWC7NGXXnDA4WkkbPH8pvSbwyCR2Auyw==", + "hasInstallScript": true, + "license": "SEE LICENSE IN README.md", + "bin": { + "claude": "bin/claude.exe" + }, + "engines": { + "node": ">=22.0.0" + }, + "optionalDependencies": { + "@anthropic-ai/claude-code-darwin-arm64": "2.1.220", + "@anthropic-ai/claude-code-darwin-x64": "2.1.220", + "@anthropic-ai/claude-code-linux-arm64": "2.1.220", + "@anthropic-ai/claude-code-linux-arm64-musl": "2.1.220", + "@anthropic-ai/claude-code-linux-x64": "2.1.220", + "@anthropic-ai/claude-code-linux-x64-musl": "2.1.220", + "@anthropic-ai/claude-code-win32-arm64": "2.1.220", + "@anthropic-ai/claude-code-win32-x64": "2.1.220" + } + }, + "node_modules/@anthropic-ai/claude-code-darwin-arm64": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-arm64/-/claude-code-darwin-arm64-2.1.220.tgz", + "integrity": "sha512-rmtd41Bf+n+YnhjSjtQ8WG5qy8KKogUp3YRfQrkLsTgPUD0H3j869rBInBJT3SHrKQ0hLghQLGM73CC1C+USLQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-code-darwin-x64": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-darwin-x64/-/claude-code-darwin-x64-2.1.220.tgz", + "integrity": "sha512-hbuoG+YCo37VzSKzKJ47ymRmt/YjASc3dRcsZtCcftLYdopv8KL889x/IbCl3cfp/VqV2rRDZ0f3aUDpHUFweQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-arm64": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64/-/claude-code-linux-arm64-2.1.220.tgz", + "integrity": "sha512-VHFI8mKruIntKn7eq81sbyS19/KWmQcmJQsS/C+j9M/E+w0s4UytgsL7DADPjBE/GByNiKoRtLYDMntCjRlOdA==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-arm64-musl": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-arm64-musl/-/claude-code-linux-arm64-musl-2.1.220.tgz", + "integrity": "sha512-m37ALw8jcbSknuyG7xDQjGPY7Gth3eX8iFY1XFEWABVq1iUMVAUn96WC9eqwi8/JSqyG2t3oNRiqHdi2ZNKFGQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-x64": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64/-/claude-code-linux-x64-2.1.220.tgz", + "integrity": "sha512-3CGFCnI0gpgsqNeJruFALBDGJaKXOuok3alQEg56ty2yOPpIrOx/r2Y0+T4uhJl7kP5Hzw4IFkxo4DZKWvzQ7Q==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-linux-x64-musl": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-linux-x64-musl/-/claude-code-linux-x64-musl-2.1.220.tgz", + "integrity": "sha512-+QyT1KikOdMRKReWFaBYGsroYx2vEjjx54DwhMoC24oE1DxjC+SlKjeOTRXAKiu0fr0O549Lkhg2tuT5xtQpAQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@anthropic-ai/claude-code-win32-arm64": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-arm64/-/claude-code-win32-arm64-2.1.220.tgz", + "integrity": "sha512-APqZwFBn38DBUwB65uUTetW7lbtUqFfAfOWKvkmOyqFDswDEsInaINuIwqMCl44WYcch10SaHhEZdXJU9MG3aQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/claude-code-win32-x64": { + "version": "2.1.220", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code-win32-x64/-/claude-code-win32-x64-2.1.220.tgz", + "integrity": "sha512-UGrjH8cGhC6PzhTyZSdgf/RpKxpfk9XJZ/RT/wsG2AJg9yEJLjLg6/TrnlL8RFbEv6Zahu0Quytc02UOpA/GiA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@anthropic-ai/sdk": { + "version": "0.71.2", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.71.2.tgz", + "integrity": "sha512-TGNDEUuEstk/DKu0/TflXAEt+p+p/WhTlFzEnoosvbaDU2LTjm42igSdlL0VijrKpWejtOKxX0b8A7uc+XiSAQ==", + "license": "MIT", + "dependencies": { + "json-schema-to-ts": "^3.1.1" + }, + "bin": { + "anthropic-ai-sdk": "bin/cli" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.10", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz", + "integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.1.1", + "@csstools/css-color-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", + "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.4", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", "dev": true, "license": "MIT", "dependencies": { @@ -816,6 +1424,101 @@ "react": ">=16.8.0" } }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz", + "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, "node_modules/@emnapi/runtime": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", @@ -1684,6 +2387,19 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "dev": true, @@ -1709,6 +2425,29 @@ "version": "1.1.1", "license": "MIT" }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/@mixmark-io/domino": { "version": "2.2.0", "dev": true, @@ -2193,6 +2932,10 @@ "resolved": "packages/core", "link": true }, + "node_modules/@propr/desktop": { + "resolved": "apps/desktop", + "link": true + }, "node_modules/@propr/local-setup": { "resolved": "packages/local-setup", "link": true @@ -2896,890 +3639,1360 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/express-session": { + "version": "1.18.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/fast-levenshtein": { + "version": "0.0.4", + "license": "MIT" + }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.10", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*", + "@types/node": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.17.21", + "license": "MIT" + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "license": "MIT" + }, + "node_modules/@types/multer": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/node": { + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/oauth": { + "version": "0.9.6", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/parse-path": { + "version": "7.0.3", + "license": "MIT" + }, + "node_modules/@types/passport": { + "version": "1.0.17", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/passport-github2": { + "version": "1.2.9", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/passport": "*", + "@types/passport-oauth2": "*" + } + }, + "node_modules/@types/passport-oauth2": { + "version": "1.8.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "license": "MIT" + }, + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-syntax-highlighter": { + "version": "15.5.13", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" + } + }, + "node_modules/@types/turndown": { + "version": "5.0.6", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/unist": { + "version": "3.0.3", "license": "MIT" }, - "node_modules/@types/estree-jsx": { - "version": "1.0.5", + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "license": "MIT" + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-push": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/@types/web-push/-/web-push-3.6.4.tgz", + "integrity": "sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==", + "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "*" + "@types/node": "*" } }, - "node_modules/@types/express": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" + "@types/node": "*" } }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", - "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", + "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/type-utils": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.66.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/express-session": { - "version": "1.18.2", + "node_modules/@typescript-eslint/parser": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, "license": "MIT", "dependencies": { - "@types/express": "*" + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/fast-levenshtein": { - "version": "0.0.4", - "license": "MIT" - }, - "node_modules/@types/fs-extra": { - "version": "11.0.4", + "node_modules/@typescript-eslint/project-service": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", "dependencies": { - "@types/jsonfile": "*", - "@types/node": "*" + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/hast": { - "version": "3.0.4", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, "license": "MIT", "dependencies": { - "@types/unist": "*" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/jsonfile": { - "version": "6.1.4", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/jsonwebtoken": { - "version": "9.0.10", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", + "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", "dev": true, "license": "MIT", "dependencies": { - "@types/ms": "*", - "@types/node": "*" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/lodash": { - "version": "4.17.21", - "license": "MIT" - }, - "node_modules/@types/mdast": { - "version": "4.0.4", + "node_modules/@typescript-eslint/types": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/unist": "*" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/ms": { - "version": "2.1.0", - "license": "MIT" - }, - "node_modules/@types/multer": { - "version": "2.0.0", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "dev": true, "license": "MIT", "dependencies": { - "@types/express": "*" + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/node": { - "version": "22.20.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", - "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", + "node_modules/@typescript-eslint/utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/oauth": { - "version": "0.9.6", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/parse-path": { - "version": "7.0.3", - "license": "MIT" + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "license": "ISC" }, - "node_modules/@types/passport": { - "version": "1.0.17", + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", "dev": true, "license": "MIT", "dependencies": { - "@types/express": "*" + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/@types/passport-github2": { - "version": "1.2.9", + "node_modules/@vitest/expect": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", + "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", "dev": true, "license": "MIT", "dependencies": { - "@types/express": "*", - "@types/passport": "*", - "@types/passport-oauth2": "*" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.4", + "@vitest/utils": "4.1.4", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/passport-oauth2": { - "version": "1.8.0", + "node_modules/@vitest/mocker": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", + "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", "dev": true, "license": "MIT", "dependencies": { - "@types/express": "*", - "@types/oauth": "*", - "@types/passport": "*" + "@vitest/spy": "4.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/@types/prismjs": { - "version": "1.26.5", - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.17", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", - "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", + "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", + "dev": true, "license": "MIT", "dependencies": { - "csstype": "^3.2.2" + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/react-dom": { - "version": "19.2.3", + "node_modules/@vitest/runner": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", + "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", "dev": true, "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" + "dependencies": { + "@vitest/utils": "4.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/react-syntax-highlighter": { - "version": "15.5.13", + "node_modules/@vitest/snapshot": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", + "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/react": "*" + "@vitest/pretty-format": "4.1.4", + "@vitest/utils": "4.1.4", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", + "node_modules/@vitest/spy": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", + "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", + "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", + "node_modules/@vitest/utils": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", + "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "dev": true, "license": "MIT", "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" + "@vitest/pretty-format": "4.1.4", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/@types/turndown": { - "version": "5.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/unist": { - "version": "3.0.3", - "license": "MIT" - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.6", - "license": "MIT" - }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/web-push": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/@types/web-push/-/web-push-3.6.4.tgz", - "integrity": "sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==", + "node_modules/@xmldom/xmldom": { + "version": "0.9.12", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.12.tgz", + "integrity": "sha512-5AXjrcMClTryPe9LgZrygpB1lj7s0S9E0+W+AHaVKAVyHanafK86iPSvG5xHVSp/jC+VH1UXu0TAEmY279xH7A==", "dev": true, "license": "MIT", - "dependencies": { - "@types/node": "*" + "engines": { + "node": ">=14.6" } }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "node_modules/accepts": { + "version": "1.3.8", "license": "MIT", "dependencies": { - "@types/node": "*" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", - "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/type-utils": "8.66.0", - "@typescript-eslint/utils": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.66.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", - "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", - "dev": true, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", - "debug": "^4.4.3" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 14" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", - "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.66.0", - "@typescript-eslint/types": "^8.66.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", - "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", - "dev": true, + "node_modules/ajv-formats": { + "version": "3.0.1", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0" + "ajv": "^8.0.0" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "ajv": "^8.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", - "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", - "dev": true, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", - "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", - "dev": true, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/utils": "8.66.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" + "environment": "^1.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", - "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", - "dev": true, + "node_modules/ansi-regex": { + "version": "6.2.2", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", - "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.66.0", - "@typescript-eslint/tsconfig-utils": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", - "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "node_modules/any-promise": { + "version": "1.3.0", "dev": true, - "license": "MIT", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "license": "ISC", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", - "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "node_modules/append-field": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/arg": { + "version": "5.0.2", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/asn1.js": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", + "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.66.0", - "eslint-visitor-keys": "^5.0.0" - }, + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=12" + } + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">= 4.0.0" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "license": "ISC" + "node_modules/atomic-sleep": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", + "node_modules/author-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/author-regex/-/author-regex-1.0.0.tgz", + "integrity": "sha512-KbWgR8wOYRAPekEmMXrYYdc7BRyhn2Ftk7KWfMUnQ43hFdojWEFRxhhRUm3/OFEdPa1r0KAvTTg9YQK57xTe0g==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, + "optional": true, "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "node": ">=0.8" } }, - "node_modules/@vitest/expect": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", - "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", - "dev": true, + "node_modules/auto-bind": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", + "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", + "node_modules/autoprefixer": { + "version": "10.4.23", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" }, - "funding": { - "url": "https://opencollective.com/vitest" + "bin": { + "autoprefixer": "bin/autoprefixer" }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": "^10 || ^12 || >=14" }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } + "peerDependencies": { + "postcss": "^8.1.0" } }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", - "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", - "dev": true, + "node_modules/bail": { + "version": "2.0.2", "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, "funding": { - "url": "https://opencollective.com/vitest" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@vitest/runner": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", - "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", - "dev": true, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.4", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@vitest/snapshot": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", - "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", - "dev": true, + "node_modules/base64-js": { + "version": "1.5.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/@vitest/spy": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", - "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", - "dev": true, + "node_modules/base64url": { + "version": "3.0.1", "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" + "engines": { + "node": ">=6.0.0" } }, - "node_modules/@vitest/utils": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", - "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", + "node_modules/baseline-browser-mapping": { + "version": "2.9.7", "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "license": "Apache-2.0" + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.4", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" } }, - "node_modules/accepts": { - "version": "1.3.8", + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" + "require-from-string": "^2.0.2" } }, - "node_modules/acorn": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", - "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", - "dev": true, + "node_modules/binary-extensions": { + "version": "2.3.0", "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, + "node_modules/bindings": { + "version": "1.5.0", "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "dependencies": { + "file-uri-to-path": "1.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "node_modules/bl": { + "version": "4.1.0", "license": "MIT", - "engines": { - "node": ">= 14" + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" } }, - "node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, + "node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" + }, + "engines": { + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ajv-formats": { - "version": "3.0.1", + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "node_modules/body-parser/node_modules/media-typer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "engines": { + "node": ">= 0.8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "license": "MIT" + "node_modules/body-parser/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "node_modules/body-parser/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "environment": "^1.0.0" + "mime-db": "^1.54.0" }, "engines": { "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ansi-regex": { - "version": "6.2.2", + "node_modules/body-parser/node_modules/type-is": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", + "dependencies": { + "content-type": "^2.0.0", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, "engines": { - "node": ">=12" + "node": ">= 18" }, "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/boolbase": { + "version": "1.0.0", "dev": true, + "license": "ISC" + }, + "node_modules/boundary": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", + "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", + "license": "BSD-2-Clause" + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=8" + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=8" } }, - "node_modules/any-promise": { - "version": "1.3.0", + "node_modules/browserslist": { + "version": "4.28.1", "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "license": "ISC", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" }, "engines": { - "node": ">= 8" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "node_modules/buffer": { + "version": "5.7.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/append-field": { - "version": "1.0.0", - "license": "MIT" + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "license": "BSD-3-Clause" }, - "node_modules/arg": { - "version": "5.0.2", - "dev": true, + "node_modules/buffer-from": { + "version": "1.1.2", "license": "MIT" }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" + "node_modules/bullmq": { + "version": "5.81.3", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.81.3.tgz", + "integrity": "sha512-Q7uEH2G92rVjX3Yl2qw3+6VEO15uxehC6DfeTHQBQTehddcRDSDuquD9zuvDjxiCffXsPUaBwrRlvlbVcgKs7g==", + "license": "MIT", + "dependencies": { + "cron-parser": "4.9.0", + "ioredis": "5.11.1", + "msgpackr": "2.0.5", + "node-abort-controller": "3.1.1", + "semver": "7.8.5", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=12.22.0" + }, + "peerDependencies": { + "redis": ">=5.0.0" + }, + "peerDependenciesMeta": { + "redis": { + "optional": true + } + } }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", + "node_modules/busboy": { + "version": "1.6.0", "dependencies": { - "dequal": "^2.0.3" + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" } }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", "license": "MIT", "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/atomic-sleep": { - "version": "1.0.0", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8.0.0" + "node": ">=6" } }, - "node_modules/auto-bind": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-5.0.1.tgz", - "integrity": "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg==", + "node_modules/camelcase-css": { + "version": "2.0.1", + "dev": true, "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 6" } }, - "node_modules/autoprefixer": { - "version": "10.4.23", + "node_modules/caniuse-lite": { + "version": "1.0.30001760", "dev": true, "funding": [ { "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "url": "https://opencollective.com/browserslist" }, { "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" }, { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001760", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=10" }, - "peerDependencies": { - "postcss": "^8.1.0" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/bail": { + "node_modules/character-entities": { "version": "2.0.2", "license": "MIT", "funding": { @@ -3787,1196 +5000,1354 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "node_modules/character-entities-html4": { + "version": "2.1.0", "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/base64-js": { - "version": "1.5.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/base64id": { - "version": "2.0.0", + "node_modules/character-entities-legacy": { + "version": "3.0.0", "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/base64url": { - "version": "3.0.1", + "node_modules/character-reference-invalid": { + "version": "2.0.1", "license": "MIT", - "engines": { - "node": ">=6.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.7", + "node_modules/cheerio": { + "version": "1.1.2", "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } - }, - "node_modules/before-after-hook": { - "version": "4.0.0", - "license": "Apache-2.0" - }, - "node_modules/better-sqlite3": { - "version": "11.10.0", - "hasInstallScript": true, "license": "MIT", "dependencies": { - "bindings": "^1.5.0", - "prebuild-install": "^7.1.1" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.2.2", + "encoding-sniffer": "^0.2.1", + "htmlparser2": "^10.0.0", + "parse5": "^7.3.0", + "parse5-htmlparser2-tree-adapter": "^7.1.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^7.12.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20.18.1" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "node_modules/cheerio-select": { + "version": "2.1.0", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "require-from-string": "^2.0.2" + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", + "node_modules/chokidar": { + "version": "3.6.0", "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, "engines": { - "node": ">=8" + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/bindings": { - "version": "1.5.0", - "license": "MIT", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "license": "ISC", "dependencies": { - "file-uri-to-path": "1.0.0" + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" } }, - "node_modules/bl": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" + "node_modules/chownr": { + "version": "1.1.4", + "license": "ISC" + }, + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0" } }, - "node_modules/bn.js": { - "version": "4.12.5", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", - "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "node_modules/cli-boxes": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-4.0.1.tgz", + "integrity": "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==", "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, "engines": { - "node": ">=18" + "node": ">=18.20 <19 || >=20.10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "license": "MIT", + "dependencies": { + "restore-cursor": "^4.0.0" + }, "engines": { - "node": ">=18" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser/node_modules/media-typer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", - "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", + "node_modules/cli-truncate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-6.0.0.tgz", + "integrity": "sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==", "license": "MIT", + "dependencies": { + "slice-ansi": "^9.0.0", + "string-width": "^8.2.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=22" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "node_modules/cli-truncate/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/body-parser/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "node_modules/cli-truncate/node_modules/slice-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-9.0.0.tgz", + "integrity": "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==", "license": "MIT", "dependencies": { - "mime-db": "^1.54.0" + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" }, "engines": { - "node": ">=18" + "node": ">=22" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/body-parser/node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", + "node_modules/cli-truncate/node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", "license": "MIT", "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" }, "engines": { - "node": ">= 18" + "node": ">=20" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boolbase": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/boundary": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/boundary/-/boundary-2.0.0.tgz", - "integrity": "sha512-rJKn5ooC9u8q13IMCrW0RSp31pxBCHE3y9V/tp3TdWSLf8Em3p6Di4NBpfzbJge9YjjFEsD0RtFEjtvHL5VyEA==", - "license": "BSD-2-Clause" - }, - "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "node_modules/clsx": { + "version": "2.1.1", "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, "engines": { - "node": "20 || >=22" + "node": ">=6" } }, - "node_modules/braces": { - "version": "3.0.3", + "node_modules/cluster-key-slot": { + "version": "1.1.2", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/code-excerpt": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", + "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", "license": "MIT", "dependencies": { - "fill-range": "^7.1.1" + "convert-to-spaces": "^2.0.1" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/browserslist": { - "version": "4.28.1", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" + "color-name": "~1.1.4" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=7.0.0" } }, - "node_modules/buffer": { - "version": "5.7.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "license": "BSD-3-Clause" + "node_modules/commander": { + "version": "10.0.1", + "license": "MIT", + "engines": { + "node": ">=14" + } }, - "node_modules/buffer-from": { - "version": "1.1.2", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, - "node_modules/bullmq": { - "version": "5.81.3", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.81.3.tgz", - "integrity": "sha512-Q7uEH2G92rVjX3Yl2qw3+6VEO15uxehC6DfeTHQBQTehddcRDSDuquD9zuvDjxiCffXsPUaBwrRlvlbVcgKs7g==", + "node_modules/concat-stream": { + "version": "2.0.0", + "engines": [ + "node >= 6.0" + ], "license": "MIT", "dependencies": { - "cron-parser": "4.9.0", - "ioredis": "5.11.1", - "msgpackr": "2.0.5", - "node-abort-controller": "3.1.1", - "semver": "7.8.5", - "tslib": "2.8.1" - }, + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/connect-redis": { + "version": "9.0.0", + "license": "MIT", "engines": { - "node": ">=12.22.0" + "node": ">=18" }, "peerDependencies": { - "redis": ">=5.0.0" - }, - "peerDependenciesMeta": { - "redis": { - "optional": true - } + "express-session": ">=1", + "redis": ">=5" } }, - "node_modules/busboy": { - "version": "1.6.0", - "dependencies": { - "streamsearch": "^1.1.0" + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "license": "MIT", "engines": { - "node": ">=10.16.0" + "node": ">= 0.6" } }, - "node_modules/bytes": { - "version": "3.1.2", + "node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-to-spaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", + "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", + "node_modules/cookie": { + "version": "0.7.2", "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, "engines": { - "node": ">= 0.4" + "node": ">= 0.6" } }, - "node_modules/call-bound": { - "version": "1.0.4", + "node_modules/cookie-signature": { + "version": "1.0.7", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, + "node_modules/cron-parser": { + "version": "4.9.0", "license": "MIT", + "dependencies": { + "luxon": "^3.2.1" + }, "engines": { - "node": ">=6" + "node": ">=12.0.0" } }, - "node_modules/camelcase-css": { - "version": "2.0.1", + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", "dev": true, "license": "MIT", + "optional": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">= 6" + "node": ">= 8" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001760", + "node_modules/cross-zip": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cross-zip/-/cross-zip-4.0.1.tgz", + "integrity": "sha512-n63i0lZ0rvQ6FXiGQ+/JFCKAUyPFhLQYJIqKaa+tSJtfKeULF/IDNDAbdnSIxgS4NTuw2b0+lj8LzfITuq+ZxQ==", "dev": true, "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/ai" + "type": "consulting", + "url": "https://feross.org/support" } ], - "license": "CC-BY-4.0" - }, - "node_modules/ccount": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=12.10" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/css-select": { + "version": "5.2.2", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-reference-invalid": { - "version": "2.0.1", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/cheerio": { - "version": "1.1.2", + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", "dev": true, "license": "MIT", "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.2.2", - "encoding-sniffer": "^0.2.1", - "htmlparser2": "^10.0.0", - "parse5": "^7.3.0", - "parse5-htmlparser2-tree-adapter": "^7.1.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^7.12.0", - "whatwg-mimetype": "^4.0.0" + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" }, "engines": { - "node": ">=20.18.1" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/cheerio-select": { - "version": "2.1.0", + "node_modules/css-what": { + "version": "6.2.2", "dev": true, "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" + "engines": { + "node": ">= 6" }, "funding": { "url": "https://github.com/sponsors/fb55" } }, - "node_modules/chokidar": { - "version": "3.6.0", + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "dev": true, "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "bin": { + "cssesc": "bin/cssesc" }, "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=4" } }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", + "node_modules/csstype": { + "version": "3.2.3", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "internmap": "1 - 2" }, "engines": { - "node": ">= 6" + "node": ">=12" } }, - "node_modules/chownr": { - "version": "1.1.4", - "license": "ISC" + "node_modules/d3-color": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } }, - "node_modules/cli-boxes": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-4.0.1.tgz", - "integrity": "sha512-5IOn+jcCEHEraYolBPs/sT4BxYCe2nHg374OPiItB1O96KZFseS2gthU4twyYzeDcFew4DaUM/xwc5BQf08JJw==", - "license": "MIT", + "node_modules/d3-ease": { + "version": "3.0.1", + "license": "BSD-3-Clause", "engines": { - "node": ">=18.20 <19 || >=20.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/cli-truncate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-6.0.0.tgz", - "integrity": "sha512-3+YKIUFsohD9MIoOFPFBldjAlnfCmCDcqe6aYGFqlDTRKg80p4wg35L+j83QQ63iOlKRccEkbn8IuM++HsgEjA==", - "license": "MIT", + "node_modules/d3-format": { + "version": "3.1.0", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "license": "ISC", "dependencies": { - "slice-ansi": "^9.0.0", - "string-width": "^8.2.0" + "d3-color": "1 - 3" }, "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/cli-truncate/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", + "node_modules/d3-path": { + "version": "3.1.0", + "license": "ISC", "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/slice-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-9.0.0.tgz", - "integrity": "sha512-SO/3iYL5S3W57LLEniscOGPZgOqZUPCx6d3dB+52B80yJ0XstzsC/eV8gnA4tM3MHDrKz+OCFSLNjswdSC+/bA==", - "license": "MIT", + "node_modules/d3-scale": { + "version": "4.0.2", + "license": "ISC", "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" }, "engines": { - "node": ">=22" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" + "node": ">=12" } }, - "node_modules/cli-truncate/node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "license": "MIT", + "node_modules/d3-shape": { + "version": "3.2.0", + "license": "ISC", "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" + "d3-path": "^3.1.0" }, "engines": { - "node": ">=20" + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=12" } }, - "node_modules/clsx": { - "version": "2.1.1", - "license": "MIT", + "node_modules/d3-time-format": { + "version": "4.1.0", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, "engines": { - "node": ">=6" + "node": ">=12" } }, - "node_modules/cluster-key-slot": { - "version": "1.1.2", - "license": "Apache-2.0", + "node_modules/d3-timer": { + "version": "3.0.1", + "license": "ISC", "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/code-excerpt": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", "license": "MIT", - "dependencies": { - "convert-to-spaces": "^2.0.1" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 12" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" }, "engines": { - "node": ">=7.0.0" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20" + } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", + "node_modules/dateformat": { + "version": "4.6.3", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": "*" } }, - "node_modules/commander": { - "version": "10.0.1", + "node_modules/debug": { + "version": "4.4.3", "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=14" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, "license": "MIT" }, - "node_modules/concat-stream": { - "version": "2.0.0", - "engines": [ - "node >= 6.0" - ], - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.0.2", - "typedarray": "^0.0.6" - } + "node_modules/decimal.js-light": { + "version": "2.5.1", + "license": "MIT" }, - "node_modules/connect-redis": { - "version": "9.0.0", + "node_modules/decode-named-character-reference": { + "version": "1.2.0", "license": "MIT", - "engines": { - "node": ">=18" + "dependencies": { + "character-entities": "^2.0.0" }, - "peerDependencies": { - "express-session": ">=1", - "redis": ">=5" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "node_modules/decompress-response": { + "version": "6.0.0", "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, "engines": { - "node": ">=18" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/content-type": { - "version": "1.0.5", + "node_modules/deep-extend": { + "version": "0.6.0", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=4.0.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", + "node_modules/deep-is": { + "version": "0.1.4", "dev": true, "license": "MIT" }, - "node_modules/convert-to-spaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", - "license": "MIT", + "node_modules/denque": { + "version": "2.1.0", + "license": "Apache-2.0", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=0.10" } }, - "node_modules/cookie": { - "version": "0.7.2", + "node_modules/depd": { + "version": "2.0.0", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, - "node_modules/cookie-signature": { - "version": "1.0.7", - "license": "MIT" - }, - "node_modules/cors": { - "version": "2.8.5", + "node_modules/dequal": { + "version": "2.0.3", "license": "MIT", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, "engines": { - "node": ">= 0.10" + "node": ">=6" } }, - "node_modules/cron-parser": { - "version": "4.9.0", - "license": "MIT", - "dependencies": { - "luxon": "^3.2.1" - }, + "node_modules/detect-libc": { + "version": "2.1.2", + "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">=8" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", + "node_modules/devlop": { + "version": "1.1.0", "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "dequal": "^2.0.0" }, - "engines": { - "node": ">= 8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/css-select": { - "version": "5.2.2", + "node_modules/didyoumean": { + "version": "1.2.2", "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } + "license": "Apache-2.0" }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "node_modules/dlv": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true, "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } + "peer": true }, - "node_modules/css-what": { - "version": "6.2.2", + "node_modules/dom-serializer": { + "version": "2.0.0", "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, "funding": { - "url": "https://github.com/sponsors/fb55" + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "node_modules/domelementtype": { + "version": "2.3.0", "dev": true, - "license": "MIT" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" }, - "node_modules/cssesc": { - "version": "3.0.0", + "node_modules/domhandler": { + "version": "5.0.3", "dev": true, - "license": "MIT", - "bin": { - "cssesc": "bin/cssesc" + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" }, "engines": { - "node": ">=4" + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/csstype": { - "version": "3.2.3", - "license": "MIT" - }, - "node_modules/d3-array": { - "version": "3.2.4", - "license": "ISC", + "node_modules/domutils": { + "version": "3.2.2", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "internmap": "1 - 2" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" } }, - "node_modules/d3-color": { - "version": "3.1.0", - "license": "ISC", + "node_modules/dotenv": { + "version": "16.5.0", + "license": "BSD-2-Clause", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "license": "BSD-3-Clause", + "node_modules/dunder-proto": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/d3-format": { - "version": "3.1.0", - "license": "ISC", - "engines": { - "node": ">=12" - } + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "license": "ISC", + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "license": "Apache-2.0", "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" + "safe-buffer": "^5.0.1" } }, - "node_modules/d3-path": { - "version": "3.1.0", - "license": "ISC", - "engines": { - "node": ">=12" - } + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" }, - "node_modules/d3-scale": { - "version": "4.0.2", - "license": "ISC", + "node_modules/electron": { + "version": "44.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-44.0.0.tgz", + "integrity": "sha512-FkTqPrFPZYljdPI5b7KORGsJTd6FgUQDefl5MrU3Xz9R87pAj9JLreIjDqcRN8hJIkFHIou0o8kKzvcpT9qiRQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" }, "engines": { - "node": ">=12" + "node": ">= 22.12.0" } }, - "node_modules/d3-shape": { - "version": "3.2.0", - "license": "ISC", + "node_modules/electron-installer-common": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.4.tgz", + "integrity": "sha512-8gMNPXfAqUE5CfXg8RL0vXpLE9HAaPkgLXVoHE3BMUzogMWenf4LmwQ27BdCUrEhkjrKl+igs2IHJibclR3z3Q==", + "dev": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "d3-path": "^3.1.0" + "@electron/asar": "^3.2.5", + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "glob": "^7.1.4", + "lodash": "^4.17.15", + "parse-author": "^2.0.0", + "semver": "^7.1.1", + "tmp-promise": "^3.0.2" }, "engines": { - "node": ">=12" + "node": ">= 10.0.0" + }, + "funding": { + "url": "https://github.com/electron-userland/electron-installer-common?sponsor=1" + }, + "optionalDependencies": { + "@types/fs-extra": "^9.0.1" } }, - "node_modules/d3-time": { - "version": "3.1.0", - "license": "ISC", + "node_modules/electron-installer-common/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, "dependencies": { - "d3-array": "2 - 3" + "cross-spawn": "^7.0.1" }, "engines": { - "node": ">=12" + "node": ">= 10" } }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "license": "ISC", + "node_modules/electron-installer-common/node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "optional": true, "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" + "@types/node": "*" } }, - "node_modules/d3-timer": { - "version": "3.0.1", - "license": "ISC", + "node_modules/electron-installer-common/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, "engines": { - "node": ">=12" + "node": ">=10" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", + "node_modules/electron-installer-debian": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/electron-installer-debian/-/electron-installer-debian-3.2.0.tgz", + "integrity": "sha512-58ZrlJ1HQY80VucsEIG9tQ//HrTlG6sfofA3nRGr6TmkX661uJyu4cMPPh6kXW+aHdq/7+q25KyQhDrXvRL7jw==", + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux" + ], + "dependencies": { + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.10.2", + "fs-extra": "^9.0.0", + "get-folder-size": "^2.0.1", + "lodash": "^4.17.4", + "word-wrap": "^1.2.3", + "yargs": "^16.0.2" + }, + "bin": { + "electron-installer-debian": "src/cli.js" + }, "engines": { - "node": ">= 12" + "node": ">= 10.0.0" } }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "node_modules/electron-installer-debian/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", "dev": true, - "license": "MIT", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" + "cross-spawn": "^7.0.1" }, "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + "node": ">= 10" } }, - "node_modules/data-urls/node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "node_modules/electron-installer-debian/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", + "optional": true, "engines": { - "node": ">=20" + "node": ">=8" } }, - "node_modules/dateformat": { - "version": "4.6.3", - "license": "MIT", - "engines": { - "node": "*" + "node_modules/electron-installer-debian/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/debug": { - "version": "4.4.3", + "node_modules/electron-installer-debian/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "ms": "^2.1.3" + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "node_modules/electron-installer-debian/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT" - }, - "node_modules/decimal.js-light": { - "version": "2.5.1", - "license": "MIT" - }, - "node_modules/decode-named-character-reference": { - "version": "1.2.0", "license": "MIT", + "optional": true, "dependencies": { - "character-entities": "^2.0.0" + "ansi-regex": "^5.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "engines": { + "node": ">=8" } }, - "node_modules/decompress-response": { - "version": "6.0.0", + "node_modules/electron-installer-debian/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "mimic-response": "^3.1.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/deep-extend": { - "version": "0.6.0", + "node_modules/electron-installer-debian/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", + "dev": true, "license": "MIT", + "optional": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, "engines": { - "node": ">=4.0.0" + "node": ">=10" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/electron-installer-debian/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "license": "MIT" - }, - "node_modules/denque": { - "version": "2.1.0", - "license": "Apache-2.0", + "license": "ISC", + "optional": true, "engines": { - "node": ">=0.10" + "node": ">=10" } }, - "node_modules/depd": { - "version": "2.0.0", + "node_modules/electron-installer-redhat": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/electron-installer-redhat/-/electron-installer-redhat-3.4.0.tgz", + "integrity": "sha512-gEISr3U32Sgtj+fjxUAlSDo3wyGGq6OBx7rF5UdpIgbnpUvMN4W5uYb0ThpnAZ42VEJh/3aODQXHbFS4f5J3Iw==", + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "darwin", + "linux" + ], + "dependencies": { + "@malept/cross-spawn-promise": "^1.0.0", + "debug": "^4.1.1", + "electron-installer-common": "^0.10.2", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "word-wrap": "^1.2.3", + "yargs": "^16.0.2" + }, + "bin": { + "electron-installer-redhat": "src/cli.js" + }, "engines": { - "node": ">= 0.8" + "node": ">= 10.0.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "license": "MIT", + "node_modules/electron-installer-redhat/node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, "engines": { - "node": ">=6" + "node": ">= 10" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "license": "Apache-2.0", + "node_modules/electron-installer-redhat/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "optional": true, "engines": { "node": ">=8" } }, - "node_modules/devlop": { - "version": "1.1.0", - "license": "MIT", + "node_modules/electron-installer-redhat/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "optional": true, "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "node_modules/didyoumean": { - "version": "1.2.2", + "node_modules/electron-installer-redhat/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "optional": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/dlv": { - "version": "1.1.3", + "node_modules/electron-installer-redhat/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "node_modules/electron-installer-redhat/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, "license": "MIT", - "peer": true + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, - "node_modules/dom-serializer": { - "version": "2.0.0", + "node_modules/electron-installer-redhat/node_modules/yargs": { + "version": "16.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz", + "integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "engines": { + "node": ">=10" } }, - "node_modules/domelementtype": { - "version": "2.3.0", + "node_modules/electron-installer-redhat/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } }, - "node_modules/domhandler": { - "version": "5.0.3", + "node_modules/electron-to-chromium": { + "version": "1.5.267", "dev": true, - "license": "BSD-2-Clause", + "license": "ISC" + }, + "node_modules/electron-winstaller": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.4.tgz", + "integrity": "sha512-j9ETcBGJaXxAY/b6UBpR7LZfjdU4BAO+yvr4ifqHEdyuc3UNCy91PDGkWKY5UQ4coHNYfnwFggrqD6QPeFGAlg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, "dependencies": { - "domelementtype": "^2.3.0" + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "semver": "^7.6.3", + "temp": "^0.9.0" }, "engines": { - "node": ">= 4" + "node": ">=8.0.0" }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" } }, - "node_modules/domutils": { - "version": "3.2.2", + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "optional": true, "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "engines": { + "node": ">=6 <7 || >=8" } }, - "node_modules/dotenv": { - "version": "16.5.0", - "license": "BSD-2-Clause", + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "optional": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" + "node": ">= 4.0.0" } }, - "node_modules/dunder-proto": { - "version": "1.0.1", + "node_modules/electron/node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "license": "Apache-2.0", + "node_modules/electron/node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "^5.0.1" + "undici-types": "~7.18.0" } }, - "node_modules/ee-first": { - "version": "1.1.1", + "node_modules/electron/node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, "license": "MIT" }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "ISC" + "license": "MIT", + "optional": true }, "node_modules/encodeurl": { "version": "2.0.0", @@ -5067,6 +6438,16 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/environment": { "version": "1.1.0", "license": "MIT", @@ -5077,6 +6458,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-define-property": { "version": "1.0.1", "license": "MIT", @@ -5092,9 +6480,9 @@ } }, "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", "dev": true, "license": "MIT" }, @@ -5937,6 +7325,13 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -6472,6 +7867,14 @@ "node": ">=14.14" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC", + "optional": true + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6493,6 +7896,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gar": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/gar/-/gar-1.0.4.tgz", + "integrity": "sha512-w4n9cPWyP7aHxKxYHFQMegj7WIAsL/YX/C4Bs5Rr8s1H9M1rNtRWRsw+ovYMkXDQ5S4ZbYHsHAPmevPjPgw44w==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "dev": true, @@ -6501,6 +7913,17 @@ "node": ">=6.9.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-east-asian-width": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", @@ -6513,6 +7936,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-folder-size": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-folder-size/-/get-folder-size-2.0.1.tgz", + "integrity": "sha512-+CEb+GDCM7tkOS2wdMKTn9vU7DgnKUTuDlehkNJKNSovdCOVxs14OfKCk4cvSaR3za4gj+OBdl9opPN9xrJ0zA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "gar": "^1.0.4", + "tiny-each-async": "2.0.3" + }, + "bin": { + "get-folder-size": "bin/get-folder-size" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "license": "MIT", @@ -6601,6 +8039,29 @@ "version": "0.0.0", "license": "MIT" }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "dev": true, @@ -6612,6 +8073,40 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/globals": { "version": "16.5.0", "dev": true, @@ -7010,6 +8505,19 @@ "node": ">=8" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "license": "ISC" @@ -7091,21 +8599,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/ink/node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", - "license": "MIT", - "dependencies": { - "restore-cursor": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ink/node_modules/indent-string": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", @@ -7118,37 +8611,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ink/node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ink/node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ink/node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -8059,50 +9521,141 @@ ], "peer": true, "engines": { - "node": ">= 12.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-7.0.2.tgz", + "integrity": "sha512-rJysbR9GKIalhTbVL2tYbF2hVyDnrf7pFUZBwjPaMIdadYHmeT+EVi/Bu3qd7ETQPahTotg2WRCatXwRBW554g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^3.1.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^5.0.1", + "rfdc": "^1.3.0", + "wrap-ansi": "^8.1.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/listr2/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], + "node_modules/listr2/node_modules/cli-truncate": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", + "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "peer": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^5.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lilconfig": { - "version": "3.1.3", + "node_modules/listr2/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/listr2/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, "engines": { - "node": ">=14" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/antonk52" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, "node_modules/locate-path": { "version": "6.0.0", @@ -8159,6 +9712,111 @@ "version": "4.1.1", "license": "MIT" }, + "node_modules/log-update": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-5.0.1.tgz", + "integrity": "sha512-5UtUDQ/6edw4ofyljDNcOVJQ4c7OjDro4h3y8e1GQL5iYElYclVHJ3zeWchylvMaKnDbDilC8irOVyexnA/Slw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^5.0.0", + "cli-cursor": "^4.0.0", + "slice-ansi": "^5.0.0", + "strip-ansi": "^7.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-escapes": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-5.0.0.tgz", + "integrity": "sha512-5GFMVX8HqE/TB+FuBJGuO5XG0WrsA6ptUqoODaT/n9mmUaZFkqnBueB4leqGBCmrUHnCnC4PCZTCd0E7QQ83bA==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^1.0.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/log-update/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/type-fest": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", + "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/longest-streak": { "version": "3.1.0", "license": "MIT", @@ -9298,6 +10956,16 @@ "version": "3.1.1", "license": "MIT" }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, "node_modules/node-domexception": { "version": "1.0.0", "funding": [ @@ -9331,6 +10999,31 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/node-gyp-build-optional-packages": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", @@ -9346,6 +11039,78 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/node-releases": { "version": "2.0.27", "dev": true, @@ -9464,6 +11229,21 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "dev": true, @@ -9521,6 +11301,20 @@ "node": ">=6" } }, + "node_modules/parse-author": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", + "integrity": "sha512-yx5DfvkN8JsHL2xk2Os9oTia467qnvRgey4ahSm2X8epehBLx/gWLcy5KI+Y36ful5DzGbCS6RazqZGgy1gHNw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "author-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/parse-entities": { "version": "4.0.2", "license": "MIT", @@ -9703,6 +11497,17 @@ "node": ">=14.0.0" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "license": "MIT", @@ -9714,6 +11519,33 @@ "version": "1.0.7", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -9734,6 +11566,21 @@ "node_modules/pause": { "version": "0.0.1" }, + "node_modules/pe-library": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-1.0.1.tgz", + "integrity": "sha512-nh39Mo1eGWmZS7y+mK/dQIqg7S1lp38DpRxkyoHf0ZcUs/HDc+yyTjuOtTvSMZHmfSLuSQaX945u05Y2Q6UWZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/pg-connection-string": { "version": "2.6.2", "license": "MIT" @@ -9884,7 +11731,22 @@ "darwin" ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/plist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.1.tgz", + "integrity": "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.9.10", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" } }, "node_modules/postcss": { @@ -10038,6 +11900,32 @@ "dev": true, "license": "MIT" }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "license": "MIT", @@ -10153,6 +12041,30 @@ ], "license": "MIT" }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/property-information": { "version": "7.1.0", "license": "MIT", @@ -10474,6 +12386,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, "node_modules/read-cache": { "version": "1.0.0", "dev": true, @@ -10883,6 +12808,17 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "license": "MIT", @@ -10890,6 +12826,24 @@ "node": ">=0.10.0" } }, + "node_modules/resedit": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-2.0.3.tgz", + "integrity": "sha512-oTeemxwoMuxxTYxXUwjkrOPfngTQehlv0/HoYFNkB4uzsP1Un1A9nI8JQKGOFkxpqkC7qkMs0lUsGrvUlbLNUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^1.0.1" + }, + "engines": { + "node": ">=14", + "npm": ">=7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, "node_modules/reselect": { "version": "5.1.1", "license": "MIT" @@ -10927,6 +12881,38 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "license": "MIT", @@ -10935,6 +12921,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.62.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", @@ -11383,6 +13376,49 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/socket.io": { "version": "4.8.3", "license": "MIT", @@ -11457,6 +13493,19 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/space-separated-tokens": { "version": "2.0.2", "license": "MIT", @@ -11531,6 +13580,58 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/stringify-entities": { "version": "4.0.4", "license": "MIT", @@ -11641,6 +13742,19 @@ "node": ">= 6" } }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -11783,6 +13897,50 @@ "node": ">=8.0.0" } }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, "node_modules/terminal-size": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/terminal-size/-/terminal-size-4.0.1.tgz", @@ -11795,6 +13953,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/terser": { + "version": "5.51.2", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.51.2.tgz", + "integrity": "sha512-bWnjSNscmuI+GJze6ZupnHP8G/cTcsJF+bXCeQknk2SHQsgbNJnLrqiH9jZ2W4STPVXH2mDKKRX3iwPhc9Cn/Q==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/thenify": { "version": "3.3.1", "dev": true, @@ -11828,6 +14016,14 @@ "node": ">=8" } }, + "node_modules/tiny-each-async": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz", + "integrity": "sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==", + "dev": true, + "license": "MIT", + "optional": true + }, "node_modules/tiny-invariant": { "version": "1.3.3", "license": "MIT" @@ -11914,6 +14110,28 @@ "dev": true, "license": "MIT" }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/tmp-promise/node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -12866,6 +15084,16 @@ "node": ">=16.0.0" } }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -12879,6 +15107,17 @@ "node": ">=0.4.0" } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "optional": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/package.json b/package.json index 63a290e96..668efd4f2 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "propr-ui" ], "overrides": { + "@electron/packager": "20.3.0", + "@electron/rebuild": "4.2.0", "react": "19.2.7" }, "scripts": { @@ -68,6 +70,17 @@ "cli:pack": "node packages/cli/scripts/build-publish.mjs", "cli:publish": "node packages/cli/scripts/build-publish.mjs --publish", "deploy:hosted-ui": "npm run build -w propr-ui && npx wrangler deploy --config wrangler.hosted-ui.toml", + "desktop": "npm run dev -w @propr/desktop", + "desktop:dev": "npm run dev -w @propr/desktop", + "desktop:prepare": "npm run build -w @propr/shared && npm run build -w @propr/client", + "desktop:typecheck": "npm run typecheck -w @propr/desktop && npm run typecheck -w propr-ui", + "desktop:test": "npm run test -w @propr/desktop", + "desktop:package": "npm run package -w @propr/desktop", + "desktop:smoke": "npm run smoke:package -w @propr/desktop", + "desktop:make": "npm run make -w @propr/desktop", + "audit:runtime": "npm audit --package-lock-only --omit=dev --audit-level=low", + "desktop:audit:packaging": "npm audit --package-lock-only --workspace=@propr/desktop --include=dev --audit-level=high", + "desktop:audit": "npm run audit:runtime && npm run desktop:audit:packaging", "start:prod": "docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $PWD/.env:/app/.env:ro -v $PWD/data:/app/data -v $PWD/logs:/app/logs -v $PWD/repos:/app/repos propr/launcher:latest" }, "keywords": [], diff --git a/packages/api/corsValidation.ts b/packages/api/corsValidation.ts index c18a5d18e..5a35823a1 100644 --- a/packages/api/corsValidation.ts +++ b/packages/api/corsValidation.ts @@ -3,10 +3,11 @@ // The hosted UI origin (FRONTEND_URL, e.g. https://app.propr.dev) is always // allowed. When COOKIE_DOMAIN is set, the base domain and any of its subdomains // are also allowed so PR preview environments that share sessions via -// cross-subdomain cookies can talk to the API. localhost/127.0.0.1 are allowed -// for local development. +// cross-subdomain cookies can talk to the API. localhost/127.0.0.1/[::1] are +// allowed for local development. import type { ErrorRequestHandler } from 'express'; +import { DESKTOP_RENDERER_ORIGIN } from '@propr/shared'; export type CorsOriginCallback = (err: Error | null, allow?: boolean) => void; export type CorsOriginValidator = (origin: string | undefined, callback: CorsOriginCallback) => void; @@ -45,6 +46,13 @@ export function createCorsOriginValidator(frontendUrl: string, cookieDomain: str callback(null, true); return; } + // Electron registers this as a standard, secure scheme, which gives the + // packaged renderer a stable serialized origin. Match that origin exactly; + // never accept the generic `null` value used by arbitrary opaque origins. + if (origin === DESKTOP_RENDERER_ORIGIN) { + callback(null, true); + return; + } try { const url = new URL(origin); // Allow the base domain and any subdomain. The previous inline validator @@ -60,11 +68,11 @@ export function createCorsOriginValidator(frontendUrl: string, cookieDomain: str } else if (url.origin === frontendOrigin) { callback(null, true); } else if ( - (url.hostname === 'localhost' || url.hostname === '127.0.0.1') && + (url.hostname === 'localhost' || url.hostname === '127.0.0.1' || url.hostname === '[::1]') && (url.protocol === 'http:' || url.protocol === 'https:') ) { - // Allow localhost for development, but only over http/https so an unusual - // scheme (e.g. file:, chrome-extension:) on localhost is not trusted. + // Allow loopback hosts for development, but only over http/https so an + // unusual scheme (e.g. file:, chrome-extension:) is not trusted. callback(null, true); } else { callback(new CorsOriginError()); diff --git a/packages/api/test/corsValidation.test.ts b/packages/api/test/corsValidation.test.ts index f0b24c9e4..2e960b693 100644 --- a/packages/api/test/corsValidation.test.ts +++ b/packages/api/test/corsValidation.test.ts @@ -1,9 +1,12 @@ import assert from 'node:assert/strict'; import { once } from 'node:events'; +import { createServer } from 'node:http'; import type { AddressInfo } from 'node:net'; import { test } from 'node:test'; +import { DESKTOP_RENDERER_ORIGIN } from '@propr/shared'; import cors from 'cors'; import express from 'express'; +import { Server as SocketIOServer } from 'socket.io'; import { corsRejectionHandler, createCorsOriginValidator } from '../corsValidation.js'; // Helper that runs the validator synchronously and reports whether the origin @@ -39,21 +42,34 @@ test('CORS allows requests with no origin', () => { assert.equal(isAllowed(validate, undefined), true); }); -test('CORS allows localhost for development', () => { +test('CORS allows only the exact packaged desktop renderer custom origin', () => { + const validate = createCorsOriginValidator('https://app.propr.dev', undefined); + + assert.equal(isAllowed(validate, DESKTOP_RENDERER_ORIGIN), true); + assert.equal(isAllowed(validate, `${DESKTOP_RENDERER_ORIGIN}.evil.example`), false); + assert.equal(isAllowed(validate, 'propr-app://other-renderer'), false); + assert.equal(isAllowed(validate, 'null'), false); +}); + +test('CORS allows HTTP(S) loopback origins for development', () => { const validate = createCorsOriginValidator('https://app.propr.dev', undefined); assert.equal(isAllowed(validate, 'http://localhost:5173'), true); assert.equal(isAllowed(validate, 'http://127.0.0.1:5173'), true); + assert.equal(isAllowed(validate, 'http://[::1]:5173'), true); assert.equal(isAllowed(validate, 'https://localhost:5173'), true); + assert.equal(isAllowed(validate, 'https://[::1]:5173'), true); }); -test('CORS rejects non-http(s) localhost schemes', () => { - // Only http/https localhost origins are trusted; an unusual scheme that still - // parses with a localhost hostname must not be allowed. +test('CORS rejects unsafe schemes and non-loopback hosts', () => { + // Only http/https loopback origins are trusted; an unusual scheme that still + // parses with a loopback hostname must not be allowed. const validate = createCorsOriginValidator('https://app.propr.dev', undefined); assert.equal(isAllowed(validate, 'chrome-extension://localhost'), false); assert.equal(isAllowed(validate, 'file://localhost'), false); + assert.equal(isAllowed(validate, 'file://[::1]/tmp/propr'), false); + assert.equal(isAllowed(validate, 'http://[2001:db8::1]:5173'), false); }); test('CORS allows COOKIE_DOMAIN subdomains for preview environments', () => { @@ -132,6 +148,7 @@ for (const runtimeMode of ['development', 'production'] as const) { 'https://app.propr.dev', 'https://pr-17.preview.example.com', 'http://localhost:5173', + 'http://[::1]:5173', ]) { const response = await fetch(`${baseUrl}/api/protected`, { headers: { Origin: origin } }); assert.equal(response.status, 401, `expected ${origin} to reach authentication`); @@ -142,9 +159,10 @@ for (const runtimeMode of ['development', 'production'] as const) { assert.equal(noOrigin.status, 401); const compatibility = await fetch(`${baseUrl}/api/compatibility`, { - headers: { Origin: 'https://app.propr.dev' }, + headers: { Origin: DESKTOP_RENDERER_ORIGIN }, }); assert.equal(compatibility.status, 200); + assert.equal(compatibility.headers.get('access-control-allow-origin'), DESKTOP_RENDERER_ORIGIN); const allowedPreflight = await fetch(`${baseUrl}/api/protected`, { method: 'OPTIONS', @@ -158,3 +176,28 @@ for (const runtimeMode of ['development', 'production'] as const) { }); }); } + +test('Socket.IO applies the shared CORS validator to the packaged desktop renderer', async () => { + const server = createServer(); + const io = new SocketIOServer(server, { + cors: { + origin: createCorsOriginValidator('https://app.propr.dev', undefined), + credentials: true, + }, + }); + server.listen(0, '127.0.0.1'); + await once(server, 'listening'); + const { port } = server.address() as AddressInfo; + + try { + const response = await fetch(`http://127.0.0.1:${port}/socket.io/?EIO=4&transport=polling`, { + headers: { Origin: DESKTOP_RENDERER_ORIGIN }, + }); + + assert.equal(response.status, 200); + assert.equal(response.headers.get('access-control-allow-origin'), DESKTOP_RENDERER_ORIGIN); + assert.equal(response.headers.get('access-control-allow-credentials'), 'true'); + } finally { + await new Promise(resolve => io.close(() => resolve())); + } +}); diff --git a/packages/api/test/webPushDispatcher.test.ts b/packages/api/test/webPushDispatcher.test.ts index a58b18551..ed3e38401 100644 --- a/packages/api/test/webPushDispatcher.test.ts +++ b/packages/api/test/webPushDispatcher.test.ts @@ -13,6 +13,11 @@ import { NotificationService } from '../../core/src/services/notificationService import { WebPushDispatcher } from '../services/webPushDispatcher.js'; const success: SendResult = { statusCode: 201, body: '', headers: {} }; +const HISTORICAL_FIXTURE_TIME = Date.parse('2020-01-01T00:00:00.000Z'); + +function historicalFixtureTime(): Date { + return new Date(HISTORICAL_FIXTURE_TIME); +} function createDatabase(): Knex { return knex({ @@ -61,7 +66,7 @@ beforeEach(async () => { await addAdvertisedActions(database); notifications = new NotificationService({ database, - now: () => new Date(Date.now() - 5_000), + now: historicalFixtureTime, }); }); @@ -399,7 +404,7 @@ describe('Web Push dispatcher', { concurrency: false }, () => { assert.ok(address !== null && typeof address !== 'string'); const localNotifications = new NotificationService({ database, - now: () => new Date(Date.now() - 5_000), + now: historicalFixtureTime, allowInsecureLocalhost: true, }); await queuedEvent({ @@ -438,7 +443,7 @@ describe('Web Push dispatcher', { concurrency: false }, () => { process.env.API_PUBLIC_URL = 'http://localhost:4000'; const localNotifications = new NotificationService({ database, - now: () => new Date(Date.now() - 5_000), + now: historicalFixtureTime, allowInsecureLocalhost: true, }); await queuedEvent({ @@ -489,7 +494,7 @@ describe('Web Push dispatcher', { concurrency: false }, () => { await createNotificationSchema(database); await addPreferenceApis(database); await addAdvertisedActions(database); - notifications = new NotificationService({ database, now: () => new Date(Date.now() - 5_000) }); + notifications = new NotificationService({ database, now: historicalFixtureTime }); await queuedEvent(); const exhausted = dispatcher({ sendNotification: async () => Promise.reject({ statusCode: 503, body: 'SECRET' }), diff --git a/packages/client/test/client.test.ts b/packages/client/test/client.test.ts index a3af466bb..dae6a6b7a 100644 --- a/packages/client/test/client.test.ts +++ b/packages/client/test/client.test.ts @@ -13,6 +13,7 @@ describe('Propr API base URLs and instance profiles', () => { assert.equal(normalizeApiBaseUrl(), ''); assert.equal(normalizeApiBaseUrl(' http://localhost:4000/// '), 'http://localhost:4000'); assert.equal(normalizeApiBaseUrl('http://127.0.0.1:3000'), 'http://127.0.0.1:3000'); + assert.equal(normalizeApiBaseUrl('http://[::1]:3000'), 'http://[::1]:3000'); assert.equal(normalizeApiBaseUrl('https://propr.example.com/'), 'https://propr.example.com'); const profile = normalizeInstanceProfile({ diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index ecbb3b448..561ea2645 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -92,6 +92,7 @@ export { DEFAULT_PROPR_ROUTING_URL, DEFAULT_PROPR_GH_RELAY_URL, DEFAULT_PROPR_UI_ORIGIN, + DESKTOP_RENDERER_ORIGIN, PROPR_UI_PROXY_SUFFIX, PROPR_UI_PROXY_LABEL_PREFIX, DEFAULT_CLOUDFLARED_IMAGE, diff --git a/packages/shared/src/proprServiceUrls.ts b/packages/shared/src/proprServiceUrls.ts index 447573de9..b06cec385 100644 --- a/packages/shared/src/proprServiceUrls.ts +++ b/packages/shared/src/proprServiceUrls.ts @@ -35,6 +35,12 @@ export const DEFAULT_PROPR_GH_RELAY_URL = 'https://webhook.propr.dev/v1'; */ export const DEFAULT_PROPR_UI_ORIGIN = 'https://app.propr.dev'; +/** + * Exact browser origin used by the packaged Electron renderer. The API uses + * this value as a narrow CORS exception for desktop REST and Socket.IO calls. + */ +export const DESKTOP_RENDERER_ORIGIN = 'propr-app://renderer'; + /** * DNS suffix and label prefix for per-instance UI/API tunnel hostnames. Each * local stack with an instance id is reachable at diff --git a/propr-ui/src/App.tsx b/propr-ui/src/App.tsx index 96a4a3180..dd427691d 100644 --- a/propr-ui/src/App.tsx +++ b/propr-ui/src/App.tsx @@ -1,5 +1,5 @@ import React, { lazy, Suspense, useCallback, useEffect, useRef, useState } from 'react' -import { BrowserRouter as Router, Routes, Route, Link, useLocation, useNavigate } from 'react-router-dom' +import { BrowserRouter, HashRouter, Routes, Route, Link, useLocation, useNavigate } from 'react-router-dom' import Layout from './components/Layout' import { ToastProvider } from './components/ui/Toast' import { SocketProvider } from './contexts/SocketProvider' @@ -21,8 +21,11 @@ import RouteChunkErrorBoundary from './components/RouteChunkErrorBoundary' import { ConnectAccountProvider } from './contexts/ConnectAccountContext' import { BrowserPushProvider } from './hooks/useBrowserPush' import { NotificationCenterProvider } from './contexts/NotificationCenterContext' +import { currentUiPathname, isDesktopRuntime, publicAssetUrl } from './config/runtimeMode' import { DesktopPresentationBoundary } from './desktop/DesktopPresentationBoundary' +const Router = isDesktopRuntime() ? HashRouter : BrowserRouter; + const AiAgentsPage = lazy(() => import('./pages/AiAgentsPage')) const AccessManagementPage = lazy(() => import('./pages/AccessManagementPage')) const Dashboard = lazy(() => import('./components/Dashboard')) @@ -38,9 +41,7 @@ const SettingsPage = lazy(() => import('./pages/SettingsPage')) const SummaryBrowserPage = lazy(() => import('./pages/SummaryBrowserPage')) const TasksPage = lazy(() => import('./pages/TasksPage')) -type CompatibilityState = - | { status: 'checking' } - | { status: 'ready' } +type CompatibilityState = { status: 'checking' } | { status: 'ready' } | { status: 'blocked'; title: string; message: string }; const AUTHORIZATION_REFRESH_INTERVAL_MS = 60_000; @@ -94,7 +95,7 @@ const HostedConnectionBlocked: React.FC<{ title: string; message: string }> = ({ const HostedOAuthCompletion: React.FC = () => (
- ProPR + ProPR

GitHub sign-in complete

You can close this window and return to ProPR.

@@ -143,7 +144,7 @@ export const NotFoundRouteContent: React.FC<{ hostname?: string }> = ({ hostname const AppContent: React.FC = () => { const { isDemoMode, isLoading: isDemoModeLoading } = useDemoMode(); // Auth check state - start loading unless already on login page - const [isLoading, setIsLoading] = useState(window.location.pathname !== '/login'); + const [isLoading, setIsLoading] = useState(currentUiPathname() !== '/login'); const [currentUser, setCurrentUser] = useState(null); const refreshPromiseRef = useRef | null>(null); @@ -164,7 +165,7 @@ const AppContent: React.FC = () => { const checkSession = async () => { // Don't check if we are already on login page - if (window.location.pathname === '/login') { + if (currentUiPathname() === '/login') { setIsLoading(false); return; } @@ -197,7 +198,7 @@ const AppContent: React.FC = () => { }, [refreshCurrentUser]); useEffect(() => { - if (isDemoMode || window.location.pathname === '/login') return; + if (isDemoMode || currentUiPathname() === '/login') return; const refreshAuthorization = () => { if (document.visibilityState === 'hidden') return; void refreshCurrentUser().catch(error => { diff --git a/propr-ui/src/api/apiClient.ts b/propr-ui/src/api/apiClient.ts index 23b9619aa..32cf33f2f 100644 --- a/propr-ui/src/api/apiClient.ts +++ b/propr-ui/src/api/apiClient.ts @@ -1,6 +1,7 @@ import { DEMO_MODE_READ_ONLY_CODE } from '@propr/shared'; import { ProprClient } from '@propr/client'; import { getApiBaseUrl, pathWithActiveHostedTunnelFlow } from '../config/runtimeConfig'; +import { currentUiPathname, navigateToUiPath } from '../config/runtimeMode'; const createProprClient = (baseUrl: string): ProprClient => new ProprClient({ baseUrl, @@ -115,10 +116,10 @@ const throwUnauthorizedResponse = (data: ApiErrorBody | null): never => { if (data?.code === TOKEN_REFRESHED_CODE) { throw new TokenRefreshRetryRequiredError(getApiErrorMessage(data)); } - if (window.location.pathname === '/login') throw new Error('Authentication required'); + if (currentUiPathname() === '/login') throw new Error('Authentication required'); // Preserve only the validated active flow so login/OAuth cannot be driven by // arbitrary raw URL input or copied sessionStorage. - window.location.href = pathWithActiveHostedTunnelFlow('/login'); + navigateToUiPath(pathWithActiveHostedTunnelFlow('/login')); throw new Error('Authentication required'); }; diff --git a/propr-ui/src/api/proprApi.logout.test.ts b/propr-ui/src/api/proprApi.logout.test.ts index 40a9ddff9..cd249051e 100644 --- a/propr-ui/src/api/proprApi.logout.test.ts +++ b/propr-ui/src/api/proprApi.logout.test.ts @@ -28,6 +28,10 @@ interface TestWindow { search: string; }; name: string; + proprDesktop?: { + auth: { logout: ReturnType }; + external: { open: ReturnType }; + }; sessionStorage: MemoryStorage; } @@ -175,4 +179,28 @@ describe('logout', () => { expect(fetchSpy).not.toHaveBeenCalled(); expect(testWindow.location.href).toBe('http://localhost:4000/api/auth/logout'); }); + + it('logs out the active Electron session and uses hash-aware login navigation', async () => { + const testWindow = stubTestWindow({ + apiBaseUrl: 'http://localhost:4000', + hostname: 'renderer', + href: 'propr-app://renderer/renderer.html#/tasks', + pathname: '/renderer.html', + }); + testWindow.location.hash = '#/tasks'; + const sessionLogout = vi.fn().mockResolvedValue(undefined); + const openExternal = vi.fn(); + testWindow.proprDesktop = { + auth: { logout: sessionLogout }, + external: { open: openExternal }, + }; + const { logout } = await importProprApi(); + + await Promise.resolve(logout()); + + expect(sessionLogout).toHaveBeenCalledWith('http://localhost:4000'); + expect(openExternal).not.toHaveBeenCalled(); + expect(testWindow.location.href).toBe('propr-app://renderer/renderer.html#/tasks'); + expect(testWindow.location.hash).toBe('/login?logged_out=true'); + }); }); diff --git a/propr-ui/src/api/proprApi.ts b/propr-ui/src/api/proprApi.ts index 5927f1688..d58d5ae1b 100644 --- a/propr-ui/src/api/proprApi.ts +++ b/propr-ui/src/api/proprApi.ts @@ -274,6 +274,11 @@ const hostedLogout = async (): Promise => { }; export const logout = (): void | Promise => { + if (typeof window !== 'undefined' && window.proprDesktop) { + return window.proprDesktop.auth.logout(API_BASE_URL).then(() => { + window.location.hash = '/login?logged_out=true'; + }); + } if (typeof window !== 'undefined' && isHostedUiOrigin(window.location.hostname) && isProprProxyUrl(API_BASE_URL)) { hostedLogoutInFlight ??= hostedLogout(); return hostedLogoutInFlight; diff --git a/propr-ui/src/components/Layout.tsx b/propr-ui/src/components/Layout.tsx index ce4736071..388da96be 100644 --- a/propr-ui/src/components/Layout.tsx +++ b/propr-ui/src/components/Layout.tsx @@ -14,6 +14,7 @@ import { QueueStatsUpdatePayload, IndexingUpdatePayload, DraftUpdatePayload } fr import { useCurrentUser, userHasPermission } from '../contexts/AuthContext'; import { ConnectCapacityBanner } from './ConnectPlusBanner'; import { useNotificationCenter } from '../contexts/NotificationCenterContext'; +import { publicAssetUrl } from '../config/runtimeMode'; import { DesktopTitleBar } from '../desktop/DesktopTitleBar'; import { useDesktop } from '../desktop/DesktopContext'; @@ -187,7 +188,7 @@ const Layout: React.FC = ({ children }) => { `}>
- ProPR + ProPR + )} +
+ +); + +export const ConnectionPlaceholder = ({ + metadata, + security, + initialApiUrl, + onConnect, +}: { + metadata: DesktopAppMetadata | null; + security: StorageSecurity | null; + initialApiUrl: string; + onConnect: (label: string, apiBaseUrl: string) => Promise; +}) => { + const [label, setLabel] = useState('Local ProPR'); + const [apiBaseUrl, setApiBaseUrl] = useState(initialApiUrl); + const [error, setError] = useState(null); + const [saving, setSaving] = useState(false); + + useEffect(() => setApiBaseUrl(initialApiUrl), [initialApiUrl]); + + const submit = async (event: React.FormEvent) => { + event.preventDefault(); + setError(null); + setSaving(true); + try { + await onConnect(label, apiBaseUrl); + } catch (caught) { + setError(caught instanceof Error ? caught.message : 'Could not save this connection.'); + } finally { + setSaving(false); + } + }; + + return ( +
+
+
+
+

ProPR Desktop

+

+ Connect to your ProPR instance +

+
+
+ Not connected +
+
+

+ Add an existing instance to open the same dashboard you use on the web. The desktop app will not + install, download, or start runtime components. +

+
+ + + {security && !security.available && ( +
+ OS-backed encryption is unavailable ({security.backend}). Profiles can still be saved, but this + app will refuse to persist credentials until secure storage is available. +
+ )} + {error &&
{error}
} + +
+
+ Local lifecycle controls and secure pairing will appear here in a later setup flow. + {metadata && Runtime: Electron on {metadata.platform} ({metadata.arch})} +
+
+
+ ); +}; + +export const DesktopRoot = () => { + const bridge = window.proprDesktop; + const [metadata, setMetadata] = useState(null); + const [security, setSecurity] = useState(null); + const [profile, setProfile] = useState(null); + const [DashboardApp, setDashboardApp] = useState(null); + const [initialApiUrl, setInitialApiUrl] = useState('http://localhost:4000'); + const [loading, setLoading] = useState(true); + const [fatalError, setFatalError] = useState(null); + + const loadDashboard = async (activeProfile: DesktopProfile) => { + window.__PROPR_CONFIG__ = { apiBaseUrl: activeProfile.apiBaseUrl }; + const application = await import('./App'); + setProfile(activeProfile); + setDashboardApp(() => application.default); + }; + + useEffect(() => { + if (!bridge) { + setFatalError('The secure desktop bridge did not load. Restart ProPR Desktop.'); + setLoading(false); + return; + } + let cancelled = false; + const unsubscribe = bridge.app.onDeepLink(value => { + try { + const deepLink = new URL(value); + if (deepLink.hostname === 'connect') { + const apiUrl = deepLink.searchParams.get('api'); + if (apiUrl) setInitialApiUrl(apiUrl); + } + } catch { + // Main validates protocol input; ignore malformed values defensively. + } + }); + void Promise.all([bridge.app.getMetadata(), bridge.storage.security(), bridge.profiles.list()]) + .then(async ([appMetadata, storageSecurity, profiles]) => { + if (cancelled) return; + setMetadata(appMetadata); + setSecurity(storageSecurity); + const active = profiles.profiles.find(item => item.id === profiles.activeProfileId); + if (active) await loadDashboard(active); + }) + .catch(error => { + if (!cancelled) setFatalError(error instanceof Error ? error.message : 'Desktop startup failed.'); + }) + .finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { + cancelled = true; + unsubscribe(); + }; + }, [bridge]); + + const connect = async (label: string, apiBaseUrl: string) => { + if (!bridge) return; + const saved = await bridge.profiles.save({ label, apiBaseUrl }); + await activateDesktopProfile(bridge.profiles, saved); + }; + + const disconnect = async () => { + if (!bridge) return; + await bridge.profiles.setActive(null); + setProfile(null); + setDashboardApp(null); + window.__PROPR_CONFIG__ = undefined; + window.location.hash = ''; + }; + + if (loading) { + return ( +
+ +
Starting ProPR Desktop…
+
+ ); + } + + if (fatalError) { + return ( +
+ +
+
+ {fatalError} +
+
+
+ ); + } + + return ( +
+ +
+ {profile && DashboardApp + ? + : } +
+
+ ); +}; + +const container = document.getElementById('root'); +if (!container) throw new Error('Root container missing in renderer.html'); +createRoot(container).render(); diff --git a/propr-ui/src/pages/LoginPage.tsx b/propr-ui/src/pages/LoginPage.tsx index c7caaaf48..c31e25ab7 100644 --- a/propr-ui/src/pages/LoginPage.tsx +++ b/propr-ui/src/pages/LoginPage.tsx @@ -9,6 +9,7 @@ import { pathWithActiveHostedTunnelFlow, } from '../config/runtimeConfig'; import { isProprProxyUrl } from '@propr/shared'; +import { publicAssetUrl } from '../config/runtimeMode'; import { useDesktop } from '../desktop/DesktopContext'; import { useRefreshCurrentUser } from '../contexts/AuthContext'; @@ -382,7 +383,7 @@ const LoginPage: React.FC = () => {
- ProPR + ProPR {loggedOut && (
diff --git a/propr-ui/src/vite-env.d.ts b/propr-ui/src/vite-env.d.ts index c3c734be4..6abae6cad 100644 --- a/propr-ui/src/vite-env.d.ts +++ b/propr-ui/src/vite-env.d.ts @@ -3,3 +3,8 @@ // Injected at build time by Vite (see vite.config.ts) — the product version // taken from the root package.json. declare const __APP_VERSION__: string; +declare const __PROPR_DESKTOP__: boolean; + +interface Window { + proprDesktop?: import('../../apps/desktop/src/shared/contract').DesktopBridge; +} diff --git a/propr-ui/vite.config.ts b/propr-ui/vite.config.ts index 0f546bd30..0f967a19c 100644 --- a/propr-ui/vite.config.ts +++ b/propr-ui/vite.config.ts @@ -42,6 +42,7 @@ export default defineConfig({ }, define: { __APP_VERSION__: JSON.stringify(rootPkg.version), + __PROPR_DESKTOP__: 'false', }, plugins: [react(), pwaShellAssetManifest()], test: { diff --git a/test/orchestratorConfig.test.mjs b/test/orchestratorConfig.test.mjs index 978053712..ae2a42526 100644 --- a/test/orchestratorConfig.test.mjs +++ b/test/orchestratorConfig.test.mjs @@ -25,9 +25,14 @@ const manifestPath = fileURLToPath(new URL('../docker/launcher/manifest.json', i function vapidKeyPair() { const ecdh = createECDH('prime256v1'); ecdh.generateKeys(); + const privateKey = ecdh.getPrivateKey(); + const canonicalPrivateKey = Buffer.alloc(32); + // OpenSSL may omit leading zero bytes from the generated P-256 scalar. + // VAPID encodes that scalar at its fixed 32-byte width. + privateKey.copy(canonicalPrivateKey, canonicalPrivateKey.length - privateKey.length); return { publicKey: ecdh.getPublicKey(undefined, 'uncompressed').toString('base64url'), - privateKey: ecdh.getPrivateKey().toString('base64url'), + privateKey: canonicalPrivateKey.toString('base64url'), }; }