From 04c1ca910a0f308b614169a2685022c22db77b95 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Wed, 25 Mar 2026 16:42:56 +0200 Subject: [PATCH 01/19] feat: setup electron --- .github/workflows/desktop-build.yml | 37 + desktop/electron-builder.json | 29 + desktop/icon.png | Bin 0 -> 27520 bytes desktop/main.ts | 117 +- desktop/package.json | 21 + desktop/preload.ts | 20 + package.json | 8 +- pnpm-lock.yaml | 2296 ++++++++++++++++++++++++++- 8 files changed, 2510 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/desktop-build.yml create mode 100644 desktop/electron-builder.json create mode 100644 desktop/icon.png create mode 100644 desktop/package.json create mode 100644 desktop/preload.ts diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml new file mode 100644 index 0000000..d889b2a --- /dev/null +++ b/.github/workflows/desktop-build.yml @@ -0,0 +1,37 @@ +name: Build Desktop app +on: + push: + branches: + - main + - feature/electron-app + pull_request: + branches: + - main +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - name: Check out Git repository + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install pnpm + uses: pnpm/action-setup@v3 + with: + version: 10 + - name: Install dependencies + run: pnpm install + - name: Build Desktop app + run: pnpm dist:desktop + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: OpenChat-${{ matrix.os }} + path: desktop/dist/* diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json new file mode 100644 index 0000000..e627261 --- /dev/null +++ b/desktop/electron-builder.json @@ -0,0 +1,29 @@ +{ + "appId": "com.openchat.app", + "productName": "OpenChat", + "directories": { + "output": "dist", + "buildResources": "assets" + }, + "files": ["dist/**/*", "package.json", "icon.png"], + "mac": { + "category": "public.app-category.social-networking", + "target": ["dmg", "zip"], + "icon": "icon.png" + }, + "win": { + "target": ["nsis", "portable"], + "icon": "icon.png" + }, + "linux": { + "target": ["AppImage", "deb"], + "icon": "icon.png", + "category": "Chat" + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": "always", + "shortcutName": "OpenChat" + } +} diff --git a/desktop/icon.png b/desktop/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f34b935313c7409e11d4556fdfc0afc86642548c GIT binary patch literal 27520 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 literal 0 HcmV?d00001 diff --git a/desktop/main.ts b/desktop/main.ts index 9f77bf2..f6dd67d 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1 +1,116 @@ -// Make Desktop app in the future +import { app, BrowserWindow, shell, Menu } from "electron"; +import path from "path"; +import isDev from "electron-is-dev"; + +import Store from "electron-store"; + +const store = new Store(); +let mainWindow: BrowserWindow | null = null; + +function createWindow() { + const windowState = store.get("windowState", { + width: 1200, + height: 800, + x: undefined, + y: undefined, + }) as { width: number; height: number; x: number | undefined; y: number | undefined }; + + mainWindow = new BrowserWindow({ + width: windowState.width, + height: windowState.height, + x: windowState.x, + y: windowState.y, + minWidth: 800, + minHeight: 600, + titleBarStyle: "hidden", + trafficLightPosition: { x: 15, y: 15 }, + webPreferences: { + nodeIntegration: false, + contextIsolation: true, + preload: path.join(__dirname, "preload.js"), + }, + icon: path.join(__dirname, "icon.png"), + }); + + mainWindow.on("resize", () => { + if (!mainWindow) return; + const { width, height, x, y } = mainWindow.getBounds(); + store.set("windowState", { width, height, x, y }); + }); + + mainWindow.on("move", () => { + if (!mainWindow) return; + const { width, height, x, y } = mainWindow.getBounds(); + store.set("windowState", { width, height, x, y }); + }); + + const url = isDev + ? "http://localhost:3000" + : `file://${path.join(__dirname, "../apps/frontend/out/index.html")}`; + + mainWindow.loadURL(url); + + // Open external links in default browser + mainWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) => { + shell.openExternal(url); + return { action: "deny" }; + }); + + mainWindow.on("closed", () => { + mainWindow = null; + }); +} + +function setAppMenu() { + const isMac = process.platform === "darwin"; + const template: any[] = [ + ...(isMac ? [{ role: "appMenu" }] : []), + { role: "fileMenu" }, + { role: "editMenu" }, + { + label: "View", + submenu: [ + { role: "reload" }, + { role: "forceReload" }, + { type: "separator" }, + { role: "resetZoom" }, + { role: "zoomIn" }, + { role: "zoomOut" }, + { type: "separator" }, + { role: "togglefullscreen" }, + ], + }, + { role: "windowMenu" }, + { + role: "help", + submenu: [ + { + label: "Learn More", + click: async () => { + await shell.openExternal("https://openchat.io"); + }, + }, + ], + }, + ]; + + const menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); +} + +app.whenReady().then(() => { + setAppMenu(); + createWindow(); +}); + +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); + } +}); + +app.on("activate", () => { + if (mainWindow === null) { + createWindow(); + } +}); diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 0000000..5521f79 --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,21 @@ +{ + "name": "@openchat/desktop", + "version": "1.0.0", + "main": "main.ts", + "scripts": { + "dev": "npx -p electron electron -r tsx/register .", + "build": "tsc", + "package": "npm run build && npx electron-builder", + "dist": "npm run build && npx electron-builder" + }, + "dependencies": { + "electron-is-dev": "^3.0.1", + "electron-store": "^10.0.0" + }, + "devDependencies": { + "electron": "^34.5.8", + "electron-builder": "^25.1.8", + "tsx": "^4.19.2", + "typescript": "^5.7.3" + } +} diff --git a/desktop/preload.ts b/desktop/preload.ts new file mode 100644 index 0000000..4844a4c --- /dev/null +++ b/desktop/preload.ts @@ -0,0 +1,20 @@ +import { contextBridge, ipcRenderer } from "electron"; + +// Expose a minimal API to the renderer process +contextBridge.exposeInMainWorld("electron", { + platform: process.platform, + send: (channel: string, data: any) => { + // Whitelist channels for security + const validChannels = ["toMain"]; + if (validChannels.includes(channel)) { + ipcRenderer.send(channel, data); + } + }, + receive: (channel: string, func: (...args: any[]) => void) => { + const validChannels = ["fromMain"]; + if (validChannels.includes(channel)) { + // Deliberately strip event as it includes `sender` + ipcRenderer.on(channel, (_event: any, ...args: any[]) => func(...args)); + } + }, +}); diff --git a/package.json b/package.json index c284a73..b062283 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "scripts": { "dev:frontend": "pnpm --filter frontend dev", "dev:backend": "pnpm --filter backend dev", - "dev:desktop": "pnpm --filter desktop dev", + "dev:desktop": "concurrently \"pnpm dev:frontend\" \"wait-on http://localhost:3000 && pnpm --filter desktop dev\"", + "build:desktop": "pnpm --filter frontend build && pnpm --filter desktop build", + "dist:desktop": "pnpm build:desktop && pnpm --filter desktop dist", "dev": "concurrently \"pnpm dev:frontend\" \"pnpm dev:backend\"", "dev:all": "pnpm -w -r --parallel dev", "start": "pnpm -w -r start", @@ -35,6 +37,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "concurrently": "^8.2.2", + "electron": "^34.5.8", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", @@ -45,7 +48,8 @@ "tsx": "^4.21.0", "tw-animate-css": "^1.4.0", "typescript": "~5.9.3", - "typescript-eslint": "^8.46.3" + "typescript-eslint": "^8.46.3", + "wait-on": "^9.0.4" }, "engines": { "node": ">=20.19" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b7d319..435e874 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -105,6 +105,9 @@ importers: concurrently: specifier: ^8.2.2 version: 8.2.2 + electron: + specifier: ^34.5.8 + version: 34.5.8 eslint: specifier: ^9.39.1 version: 9.39.1(jiti@2.6.1) @@ -138,6 +141,9 @@ importers: typescript-eslint: specifier: ^8.46.3 version: 8.47.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) + wait-on: + specifier: ^9.0.4 + version: 9.0.4 apps/backend: dependencies: @@ -336,6 +342,28 @@ importers: specifier: ^5.1.4 version: 5.1.4(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)) + desktop: + dependencies: + electron-is-dev: + specifier: ^3.0.1 + version: 3.0.1 + electron-store: + specifier: ^10.0.0 + version: 10.1.0 + devDependencies: + electron: + specifier: ^34.5.8 + version: 34.5.8 + electron-builder: + specifier: ^25.1.8 + version: 25.1.8(electron-builder-squirrel-windows@25.1.8) + tsx: + specifier: ^4.19.2 + version: 4.21.0 + typescript: + specifier: ^5.7.3 + version: 5.9.3 + packages/components: dependencies: '@openchat/lib': @@ -376,6 +404,9 @@ importers: packages: + 7zip-bin@5.2.0: + resolution: {integrity: sha512-ukTPVhqG4jNzMro2qA9HSCSSVJN3aN7tlb+hfqYCt3ER0yWroeA2VR38MNrOHLQ/cVj+DaIMad0kFCtWWowh/A==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -474,6 +505,37 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@develar/schema-utils@2.6.5': + resolution: {integrity: sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==} + engines: {node: '>= 8.9.0'} + + '@electron/asar@3.4.1': + resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} + engines: {node: '>=10.12.0'} + hasBin: true + + '@electron/get@2.0.3': + resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==} + engines: {node: '>=12'} + + '@electron/notarize@2.5.0': + resolution: {integrity: sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==} + engines: {node: '>= 10.0.0'} + + '@electron/osx-sign@1.3.1': + resolution: {integrity: sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==} + engines: {node: '>=12.0.0'} + hasBin: true + + '@electron/rebuild@3.6.1': + resolution: {integrity: sha512-f6596ZHpEq/YskUd8emYvOUne89ij8mQgjYFA5ru25QwbrRO+t1SImofdDv7kKOuWCmVOuU5tvfkbgGxIl3E/w==} + engines: {node: '>=12.13.0'} + hasBin: true + + '@electron/universal@2.0.1': + resolution: {integrity: sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==} + engines: {node: '>=16.4'} + '@emnapi/core@1.7.1': resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} @@ -848,6 +910,29 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + '@gar/promisify@1.1.3': + resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + + '@hapi/address@5.1.1': + resolution: {integrity: sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==} + engines: {node: '>=14.0.0'} + + '@hapi/formula@3.0.2': + resolution: {integrity: sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==} + + '@hapi/hoek@11.0.7': + resolution: {integrity: sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==} + + '@hapi/pinpoint@2.0.1': + resolution: {integrity: sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==} + + '@hapi/tlds@1.1.6': + resolution: {integrity: sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==} + engines: {node: '>=14.0.0'} + + '@hapi/topo@6.0.2': + resolution: {integrity: sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==} + '@hookform/resolvers@5.2.2': resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} peerDependencies: @@ -1038,6 +1123,14 @@ packages: '@livekit/protocol@1.45.0': resolution: {integrity: sha512-z22Ej7RRBFm5uVZpU7kBHOdDwZV6Hz+1crCOrse2g7yx8TcHXG0bKnOKwyN/meD233nEDlU2IHNCoT8Vq8lvtg==} + '@malept/cross-spawn-promise@2.0.0': + resolution: {integrity: sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==} + engines: {node: '>= 12.13.0'} + + '@malept/flatpak-bundler@0.4.0': + resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} + engines: {node: '>= 10.0.0'} + '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} @@ -1111,6 +1204,15 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} + '@npmcli/fs@2.1.2': + resolution: {integrity: sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + '@npmcli/move-file@2.0.1': + resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This functionality has been moved to @npmcli/fs + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -1741,6 +1843,10 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -1753,6 +1859,10 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + '@tabler/icons-react@3.35.0': resolution: {integrity: sha512-XG7t2DYf3DyHT5jxFNp5xyLVbL4hMJYJhiSdHADzAjLRYfL7AnjlRfiHDHeXxkb2N103rEIvTsBRazxXtAUz2g==} peerDependencies: @@ -1861,6 +1971,9 @@ packages: resolution: {integrity: sha512-6qbjdm1K5kizVKv9TNqhIN3doq2anRhdF2XaFMFSn4m8L22S69RV+FilvlyVT4RoJyMxtPU5rs4RpdFa/PEC7A==} peerDependencies: react: ^18 || ^19 + '@tootallnate/once@2.0.0': + resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + engines: {node: '>= 10'} '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -1895,6 +2008,9 @@ packages: '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/cacheable-request@6.0.3': + resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} + '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} @@ -1906,6 +2022,9 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/dom-mediacapture-record@1.0.22': resolution: {integrity: sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==} @@ -1918,6 +2037,12 @@ packages: '@types/express@5.0.5': resolution: {integrity: sha512-LuIQOcb6UmnF7C1PCFmEU1u2hmiHL43fgFQX67sN3H4Z+0Yk0Neo++mFsBjhOAuLzvlQeqAAkeDOZrJs9rzumQ==} + '@types/fs-extra@9.0.13': + resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + + '@types/http-cache-semantics@4.2.0': + resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} @@ -1930,6 +2055,9 @@ packages: '@types/jsonwebtoken@9.0.10': resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} + '@types/keyv@3.1.4': + resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} + '@types/mime@1.3.5': resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} @@ -1948,6 +2076,9 @@ packages: '@types/nodemailer@7.0.9': resolution: {integrity: sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==} + '@types/plist@3.0.5': + resolution: {integrity: sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==} + '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} @@ -1962,6 +2093,9 @@ packages: '@types/react@19.2.6': resolution: {integrity: sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==} + '@types/responselike@1.0.3': + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/send@0.17.6': resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} @@ -1981,6 +2115,12 @@ packages: '@types/strip-json-comments@0.0.30': resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} + '@types/verror@1.10.11': + resolution: {integrity: sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.47.0': resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2141,6 +2281,13 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@xmldom/xmldom@0.8.11': + resolution: {integrity: sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==} + engines: {node: '>=10.0.0'} + + abbrev@1.1.1: + resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -2163,13 +2310,41 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agentkeepalive@4.6.0: + resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} + engines: {node: '>= 8.0.0'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@3.5.2: + resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} + peerDependencies: + ajv: ^6.9.1 + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2193,9 +2368,39 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + app-builder-bin@5.0.0-alpha.10: + resolution: {integrity: sha512-Ev4jj3D7Bo+O0GPD2NMvJl+PGiBAfS7pUGawntBNpCbxtpncfUixqFj9z9Jme7V7s3LBGqsWZZP54fxBX3JKJw==} + + app-builder-lib@25.1.8: + resolution: {integrity: sha512-pCqe7dfsQFBABC1jeKZXQWhGcCPF3rPCXDdfqVKjIeWBcXzyC1iOWZdfFhGl+S9MyE/k//DFmC6FzuGAUudNDg==} + engines: {node: '>=14.0.0'} + peerDependencies: + dmg-builder: 25.1.8 + electron-builder-squirrel-windows: 25.1.8 + append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + aproba@2.1.0: + resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} + + archiver-utils@2.1.0: + resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} + engines: {node: '>= 6'} + + archiver-utils@3.0.4: + resolution: {integrity: sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==} + engines: {node: '>= 10'} + + archiver@5.3.2: + resolution: {integrity: sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==} + engines: {node: '>= 10'} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -2245,13 +2450,38 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + assert-plus@1.0.0: + resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} + engines: {node: '>=0.8'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + + async-exit-hook@2.0.1: + resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} + engines: {node: '>=0.12.0'} + async-function@1.0.0: resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} engines: {node: '>= 0.4'} + async@3.2.6: + resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + + atomically@2.1.1: + resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} + autoprefixer@10.4.22: resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} @@ -2267,6 +2497,9 @@ packages: resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} engines: {node: '>=4'} + axios@1.13.6: + resolution: {integrity: sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==} + axobject-query@4.1.0: resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} engines: {node: '>= 0.4'} @@ -2274,6 +2507,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -2300,16 +2537,33 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + bluebird-lst@1.0.9: + resolution: {integrity: sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==} + + bluebird@3.7.2: + resolution: {integrity: sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==} + body-parser@2.2.0: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} + boolean@3.2.0: + resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.4: + resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2319,12 +2573,25 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + builder-util-runtime@9.2.10: + resolution: {integrity: sha512-6p/gfG1RJSQeIbz8TK5aPNkoztgY1q5TgmGFMAXcY8itsGW6Y2ld1ALsZ5UJn8rog7hKF3zHx5iQbNQ8uLcRlw==} + engines: {node: '>=12.0.0'} + + builder-util@25.1.7: + resolution: {integrity: sha512-7jPjzBwEGRbwNcep0gGNpLXG9P94VA3CPAZQCzxkFXiV2GMQKlziMbY//rXPI7WKfhsvGgFXjTcXdBEwgXw9ww==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -2341,6 +2608,18 @@ packages: magicast: optional: true + cacache@16.1.3: + resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + cacheable-lookup@5.0.4: + resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} + engines: {node: '>=10.6.0'} + + cacheable-request@7.0.4: + resolution: {integrity: sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==} + engines: {node: '>=8'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -2384,12 +2663,39 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + chromium-pickle-js@0.2.0: + resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cli-truncate@2.1.0: + resolution: {integrity: sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==} + engines: {node: '>=8'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -2397,6 +2703,13 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clone-response@1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -2408,10 +2721,30 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} + commander@5.1.0: + resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} + engines: {node: '>= 6'} + + compare-version@0.1.2: + resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} + engines: {node: '>=0.10.0'} + + compress-commons@4.1.2: + resolution: {integrity: sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==} + engines: {node: '>= 10'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2424,13 +2757,23 @@ packages: engines: {node: ^14.13.0 || >=16.0.0} hasBin: true + conf@14.0.0: + resolution: {integrity: sha512-L6BuueHTRuJHQvQVc6YXYZRtN5vJUtOdCTLn0tRYYV5azfbAFcPghB5zEE40mVrV6w7slMTqUfkDomutIK14fw==} + engines: {node: '>=20'} + confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + config-file-ts@0.2.8-rc1: + resolution: {integrity: sha512-GtNECbVI82bT4RiDIzBSVuTKoSHufnU7Ce7/42bkWZJZFLjmDF2WBpVsvRkhKCfKBnTBb3qZrBwPpFBU/Myvhg==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + content-disposition@1.0.1: resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==} engines: {node: '>=18'} @@ -2461,10 +2804,28 @@ packages: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} + core-util-is@1.0.2: + resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + + crc32-stream@4.0.3: + resolution: {integrity: sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==} + engines: {node: '>= 10'} + + crc@3.8.0: + resolution: {integrity: sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==} + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -2503,6 +2864,10 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + debounce-fn@6.0.0: + resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} + engines: {node: '>=18'} + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -2529,6 +2894,10 @@ packages: supports-color: optional: true + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -2536,6 +2905,13 @@ packages: resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} engines: {node: '>=16.0.0'} + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + defer-to-connect@2.0.1: + resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} + engines: {node: '>=10'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -2547,6 +2923,13 @@ packages: defu@6.1.4: resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -2561,6 +2944,9 @@ packages: detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -2568,13 +2954,33 @@ packages: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} + dir-compare@4.2.0: + resolution: {integrity: sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==} + dlv@1.1.3: resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dmg-builder@25.1.8: + resolution: {integrity: sha512-NoXo6Liy2heSklTI5OIZbCgXC1RzrDQsZkeEwXhdOro3FT1VBOvbubvscdPnjVuQ4AMwwv61oaH96AbiYg9EnQ==} + + dmg-license@1.0.11: + resolution: {integrity: sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==} + engines: {node: '>=8'} + os: [darwin] + hasBin: true + doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} + dot-prop@9.0.0: + resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} + engines: {node: '>=18'} + + dotenv-expand@11.0.7: + resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} + engines: {node: '>=12'} + dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} @@ -2602,9 +3008,38 @@ packages: effect@3.18.4: resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==} + ejs@3.1.10: + resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} + engines: {node: '>=0.10.0'} + hasBin: true + + electron-builder-squirrel-windows@25.1.8: + resolution: {integrity: sha512-2ntkJ+9+0GFP6nAISiMabKt6eqBB0kX1QqHNWFWAXgi0VULKGisM46luRFpIBiU3u/TDmhZMM8tzvo2Abn3ayg==} + + electron-builder@25.1.8: + resolution: {integrity: sha512-poRgAtUHHOnlzZnc9PK4nzG53xh74wj2Jy7jkTrqZ0MWPoHGh1M2+C//hGeYdA+4K8w4yiVCNYoLXF7ySj2Wig==} + engines: {node: '>=14.0.0'} + hasBin: true + + electron-is-dev@3.0.1: + resolution: {integrity: sha512-8TjjAh8Ec51hUi3o4TaU0mD3GMTOESi866oRNavj9A3IQJ7pmv+MJVmdZBFGw4GFT36X7bkqnuDNYvkQgvyI8Q==} + engines: {node: '>=18'} + + electron-publish@25.1.7: + resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} + + electron-store@10.1.0: + resolution: {integrity: sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w==} + engines: {node: '>=20'} + electron-to-chromium@1.5.256: resolution: {integrity: sha512-uqYq1IQhpXXLX+HgiXdyOZml7spy4xfy42yPxcCCRjswp0fYM2X+JwCON07lqnpLEGVCj739B7Yr+FngmHBMEQ==} + electron@34.5.8: + resolution: {integrity: sha512-vxLD65mabTzYmEVa9KceMHM0+zO+vqgrhcyNVlmTd0IGV5J7XZ8v/qElm0o4YQ4wPeq7olZkUjZkBQQEdr23/g==} + engines: {node: '>= 12.20.55'} + hasBin: true + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2619,6 +3054,12 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + encoding@0.1.13: + resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + engine.io-client@6.6.3: resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==} @@ -2634,6 +3075,17 @@ packages: resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} engines: {node: '>=10.13.0'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + env-paths@3.0.0: + resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + es-abstract@1.24.0: resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} engines: {node: '>= 0.4'} @@ -2666,6 +3118,9 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + es6-error@4.1.1: + resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -2816,6 +3271,9 @@ packages: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + express@5.1.0: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} @@ -2826,6 +3284,15 @@ packages: extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + extsprintf@1.4.1: + resolution: {integrity: sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==} + engines: {'0': node >=0.6.0} + fast-check@3.23.2: resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} engines: {node: '>=8.0.0'} @@ -2847,9 +3314,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -2867,6 +3340,9 @@ packages: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -2886,6 +3362,15 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.5: resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} engines: {node: '>= 0.4'} @@ -2894,6 +3379,10 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -2923,6 +3412,29 @@ packages: resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} engines: {node: '>= 0.8'} + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.3.4: + resolution: {integrity: sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==} + engines: {node: '>=14.14'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -2941,6 +3453,11 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + gaxios@7.1.3: resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} engines: {node: '>=18'} @@ -2973,6 +3490,10 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + get-symbol-description@1.1.0: resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} @@ -3001,6 +3522,15 @@ packages: resolution: {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 + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + 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 + + global-agent@3.0.0: + resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} + engines: {node: '>=10.0'} + globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} @@ -3032,6 +3562,10 @@ packages: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} + got@11.8.6: + resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} + engines: {node: '>=10.19.0'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -3061,6 +3595,9 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -3071,14 +3608,45 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + hosted-git-info@4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + http2-wrapper@1.0.3: + resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} + engines: {node: '>=10.19.0'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + https-proxy-agent@7.0.6: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + + iconv-corefoundation@1.1.7: + resolution: {integrity: sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==} + engines: {node: ^8.11.2 || >=10} + os: [darwin] + iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -3087,6 +3655,9 @@ packages: resolution: {integrity: sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3103,6 +3674,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + infer-owner@1.0.4: + resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==} + inflight@1.0.6: resolution: {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. @@ -3117,6 +3695,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ip-address@10.1.0: + resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==} + engines: {node: '>= 12'} + ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -3148,6 +3730,10 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} + is-ci@3.0.1: + resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} + hasBin: true + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -3180,6 +3766,13 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-lambda@1.0.1: + resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -3223,6 +3816,10 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -3235,9 +3832,20 @@ packages: resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} engines: {node: '>= 0.4'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbinaryfile@4.0.10: + resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} + engines: {node: '>= 8.0.0'} + + isbinaryfile@5.0.7: + resolution: {integrity: sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==} + engines: {node: '>= 18.0.0'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -3248,6 +3856,11 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jake@10.9.4: + resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} + engines: {node: '>=10'} + hasBin: true + jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true @@ -3256,6 +3869,10 @@ packages: resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} hasBin: true + joi@18.0.2: + resolution: {integrity: sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==} + engines: {node: '>= 20'} + jose@5.10.0: resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} @@ -3283,9 +3900,18 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-safe@5.0.1: + resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@1.0.2: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true @@ -3295,6 +3921,12 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -3325,6 +3957,13 @@ packages: resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} engines: {node: '>=0.10'} + lazy-val@1.0.5: + resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + + lazystream@1.0.1: + resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} + engines: {node: '>= 0.6.3'} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -3419,6 +4058,15 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.defaults@4.2.0: + resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} + + lodash.difference@4.5.0: + resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} + + lodash.flatten@4.4.0: + resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} + lodash.includes@4.3.0: resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} @@ -3443,23 +4091,45 @@ packages: lodash.once@4.1.1: resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.union@4.6.0: + resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loglevel@1.9.2: - resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + loglevel@1.9.2: + resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==} engines: {node: '>= 0.6.0'} loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lowercase-keys@2.0.0: + resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} + engines: {node: '>=8'} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: + resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} + engines: {node: '>=10'} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + lucide-react@0.553.0: resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} peerDependencies: @@ -3471,10 +4141,18 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + make-fetch-happen@10.2.1: + resolution: {integrity: sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + map-obj@5.0.0: resolution: {integrity: sha512-2L3MIgJynYrZ3TYMriLDLWocz15okFakV6J12HXvMXDHui2x/zgChzg1u9mFFGbbGWE+GsLpQByt4POb9Or+uA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + matcher@3.0.0: + resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} + engines: {node: '>=10'} + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -3515,9 +4193,38 @@ packages: resolution: {integrity: sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==} engines: {node: '>= 0.6'} + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + mimic-response@1.0.1: + resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} + engines: {node: '>=4'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimatch@9.0.5: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} @@ -3525,10 +4232,42 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-fetch@2.1.2: + resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass-sized@1.0.3: + resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} @@ -3599,10 +4338,20 @@ packages: sass: optional: true + node-abi@3.89.0: + resolution: {integrity: sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==} + engines: {node: '>=10'} + + node-addon-api@1.7.2: + resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==} + node-addon-api@8.5.0: resolution: {integrity: sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==} engines: {node: ^18 || ^20 || >= 21} + node-api-version@0.2.1: + resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} + node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -3619,6 +4368,11 @@ packages: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true + node-gyp@9.4.1: + resolution: {integrity: sha512-OQkWKbjQKbGkMf/xqI1jjy3oCTgMKJac58G2+bjZb3fza6gW2YrCSdMQYaoTb70crvE//Gngr4f0AgVHmqHvBQ==} + engines: {node: ^12.13 || ^14.13 || >=16} + hasBin: true + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -3626,6 +4380,11 @@ packages: resolution: {integrity: sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==} engines: {node: '>=6.0.0'} + nopt@6.0.0: + resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + hasBin: true + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3634,6 +4393,15 @@ packages: resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} engines: {node: '>=0.10.0'} + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + nypm@0.6.2: resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==} engines: {node: ^14.16.0 || >=16.10.0} @@ -3685,14 +4453,26 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + own-keys@1.0.1: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-cancelable@2.1.1: + resolution: {integrity: sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==} + engines: {node: '>=8'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -3701,6 +4481,10 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -3740,6 +4524,13 @@ packages: pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + pe-library@0.4.1: + resolution: {integrity: sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==} + engines: {node: '>=12', npm: '>=6'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -3765,6 +4556,10 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + plist@3.1.0: + resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} + engines: {node: '>=10.4.0'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -3834,10 +4629,29 @@ packages: typescript: optional: true + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -3845,6 +4659,12 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@1.4.1: resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} @@ -3862,6 +4682,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-lru@5.1.1: + resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} + engines: {node: '>=10'} + quick-lru@6.1.2: resolution: {integrity: sha512-AAFUA5O1d83pIHEhJwWCq/RQcRukCkn/NSm2QsTEMle5f2hP0ChI2+3Xb051PZCkLryI/Ir1MVKviT2FIloaTQ==} engines: {node: '>=12'} @@ -3929,13 +4753,23 @@ packages: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + read-binary-file-arch@1.0.6: + resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} + hasBin: true + read-cache@1.0.0: resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} + readdir-glob@1.1.3: + resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} + readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -3956,6 +4790,17 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resedit@1.7.2: + resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} + engines: {node: '>=12', npm: '>=6'} + + resolve-alpn@1.2.1: + resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3972,6 +4817,17 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true + responselike@2.0.1: + resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -3981,10 +4837,19 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true + roarr@2.15.4: + resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} + engines: {node: '>=8.0'} + rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -4004,6 +4869,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4018,6 +4886,13 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sanitize-filename@1.6.4: + resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} + + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -4028,6 +4903,9 @@ packages: sdp@3.2.1: resolution: {integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==} + semver-compare@1.0.0: + resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4041,10 +4919,17 @@ packages: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} + serialize-error@7.0.1: + resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} + engines: {node: '>=10'} + serve-static@2.2.0: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -4092,10 +4977,25 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-update-notifier@2.0.0: + resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} + engines: {node: '>=10'} + + slice-ansi@3.0.0: + resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} + engines: {node: '>=8'} + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + socket.io-adapter@2.5.5: resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==} @@ -4111,6 +5011,14 @@ packages: resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==} engines: {node: '>=10.2.0'} + socks-proxy-agent@7.0.0: + resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} + engines: {node: '>= 10'} + + socks@2.8.7: + resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + sonner@2.0.7: resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==} peerDependencies: @@ -4131,9 +5039,20 @@ packages: spawn-command@0.0.2: resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + sprintf-js@1.1.3: + resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + + ssri@9.0.1: + resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + stable-hash@0.0.5: resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stat-mode@1.0.0: + resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} + engines: {node: '>= 6'} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -4181,6 +5100,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -4204,6 +5126,12 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + stubborn-fs@2.0.0: + resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} + + stubborn-utils@1.0.2: + resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -4222,6 +5150,10 @@ packages: engines: {node: '>=16 || 14 >=14.17'} hasBin: true + sumchecker@3.0.1: + resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} + engines: {node: '>= 8.0'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4257,6 +5189,18 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + temp-file@3.4.0: + resolution: {integrity: sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==} + thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -4272,6 +5216,13 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tmp-promise@3.0.3: + resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} + + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} + engines: {node: '>=14.14'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -4284,6 +5235,9 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + truncate-utf8-bytes@1.0.2: + resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -4349,6 +5303,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.13.1: + resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} + engines: {node: '>=10'} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -4395,6 +5353,10 @@ packages: engines: {node: '>=14.17'} hasBin: true + uint8array-extras@1.5.0: + resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} + engines: {node: '>=18'} + unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -4405,6 +5367,22 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + unique-filename@2.0.1: + resolution: {integrity: sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + unique-slug@3.0.0: + resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -4450,6 +5428,9 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + utf8-byte-length@1.0.5: + resolution: {integrity: sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==} + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -4463,6 +5444,10 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + verror@1.10.1: + resolution: {integrity: sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==} + engines: {node: '>=0.6.0'} + vite-tsconfig-paths@5.1.4: resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} peerDependencies: @@ -4511,6 +5496,14 @@ packages: yaml: optional: true + wait-on@9.0.4: + resolution: {integrity: sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==} + engines: {node: '>=20.0.0'} + hasBin: true + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-streams-polyfill@3.3.3: resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} engines: {node: '>= 8'} @@ -4519,6 +5512,9 @@ packages: resolution: {integrity: sha512-5ZZY1+lGq8LEKuDlg9M2RPJHlH3R7OVwyHqMcUsLKCgd9Wvf+QrFTCItkXXYPmrJn8H6gRLXbSgxLLdexiqHxw==} engines: {node: '>=6.0.0', npm: '>=3.10.0'} + when-exit@2.1.5: + resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} + which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -4540,6 +5536,9 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -4567,6 +5566,10 @@ packages: utf-8-validate: optional: true + xmlbuilder@15.1.1: + resolution: {integrity: sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==} + engines: {node: '>=8.0'} + xmlhttprequest-ssl@2.1.2: resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==} engines: {node: '>=0.4.0'} @@ -4582,6 +5585,9 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4590,6 +5596,9 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -4598,6 +5607,10 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zip-stream@4.1.1: + resolution: {integrity: sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==} + engines: {node: '>= 10'} + zod-validation-error@4.0.2: resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} engines: {node: '>=18.0.0'} @@ -4627,6 +5640,8 @@ packages: snapshots: + 7zip-bin@5.2.0: {} + '@alloc/quick-lru@5.2.0': {} '@babel/code-frame@7.27.1': @@ -4749,6 +5764,82 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@develar/schema-utils@2.6.5': + dependencies: + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + + '@electron/asar@3.4.1': + dependencies: + commander: 5.1.0 + glob: 7.2.3 + minimatch: 3.1.2 + + '@electron/get@2.0.3': + dependencies: + debug: 4.4.3 + env-paths: 2.2.1 + fs-extra: 8.1.0 + got: 11.8.6 + progress: 2.0.3 + semver: 6.3.1 + sumchecker: 3.0.1 + optionalDependencies: + global-agent: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@electron/notarize@2.5.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + promise-retry: 2.0.1 + transitivePeerDependencies: + - supports-color + + '@electron/osx-sign@1.3.1': + dependencies: + compare-version: 0.1.2 + debug: 4.4.3 + fs-extra: 10.1.0 + isbinaryfile: 4.0.10 + minimist: 1.2.8 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@electron/rebuild@3.6.1': + dependencies: + '@malept/cross-spawn-promise': 2.0.0 + chalk: 4.1.2 + debug: 4.4.3 + detect-libc: 2.1.2 + fs-extra: 10.1.0 + got: 11.8.6 + node-abi: 3.89.0 + node-api-version: 0.2.1 + node-gyp: 9.4.1 + ora: 5.4.1 + read-binary-file-arch: 1.0.6 + semver: 7.7.3 + tar: 6.2.1 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - supports-color + + '@electron/universal@2.0.1': + dependencies: + '@electron/asar': 3.4.1 + '@malept/cross-spawn-promise': 2.0.0 + debug: 4.4.3 + dir-compare: 4.2.0 + fs-extra: 11.3.4 + minimatch: 9.0.5 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -4989,6 +6080,24 @@ snapshots: '@floating-ui/utils@0.2.10': {} + '@gar/promisify@1.1.3': {} + + '@hapi/address@5.1.1': + dependencies: + '@hapi/hoek': 11.0.7 + + '@hapi/formula@3.0.2': {} + + '@hapi/hoek@11.0.7': {} + + '@hapi/pinpoint@2.0.1': {} + + '@hapi/tlds@1.1.6': {} + + '@hapi/topo@6.0.2': + dependencies: + '@hapi/hoek': 11.0.7 + '@hookform/resolvers@5.2.2(react-hook-form@7.71.1(react@19.2.0))': dependencies: '@standard-schema/utils': 0.3.0 @@ -5145,6 +6254,19 @@ snapshots: dependencies: '@bufbuild/protobuf': 1.10.1 + '@malept/cross-spawn-promise@2.0.0': + dependencies: + cross-spawn: 7.0.6 + + '@malept/flatpak-bundler@0.4.0': + dependencies: + debug: 4.4.3 + fs-extra: 9.1.0 + lodash: 4.17.21 + tmp-promise: 3.0.3 + transitivePeerDependencies: + - supports-color + '@napi-rs/wasm-runtime@0.2.12': dependencies: '@emnapi/core': 1.7.1 @@ -5196,6 +6318,16 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} + '@npmcli/fs@2.1.2': + dependencies: + '@gar/promisify': 1.1.3 + semver: 7.7.3 + + '@npmcli/move-file@2.0.1': + dependencies: + mkdirp: 1.0.4 + rimraf: 3.0.2 + '@pkgjs/parseargs@0.11.0': optional: true @@ -5765,6 +6897,8 @@ snapshots: '@rtsao/scc@1.1.0': {} + '@sindresorhus/is@4.6.0': {} + '@socket.io/component-emitter@3.1.2': {} '@standard-schema/spec@1.0.0': {} @@ -5775,6 +6909,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@szmarczak/http-timer@4.0.6': + dependencies: + defer-to-connect: 2.0.1 + '@tabler/icons-react@3.35.0(react@19.2.0)': dependencies: '@tabler/icons': 3.35.0 @@ -5864,6 +7002,7 @@ snapshots: dependencies: '@tanstack/query-core': 5.96.0 react: 19.2.0 + '@tootallnate/once@2.0.0': {} '@tsconfig/node10@1.0.12': {} @@ -5908,6 +7047,13 @@ snapshots: '@types/connect': 3.4.38 '@types/node': 24.10.1 + '@types/cacheable-request@6.0.3': + dependencies: + '@types/http-cache-semantics': 4.2.0 + '@types/keyv': 3.1.4 + '@types/node': 24.10.1 + '@types/responselike': 1.0.3 + '@types/connect@3.4.38': dependencies: '@types/node': 24.10.1 @@ -5920,6 +7066,10 @@ snapshots: dependencies: '@types/node': 24.10.1 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/dom-mediacapture-record@1.0.22': {} '@types/estree@1.0.8': {} @@ -5937,6 +7087,12 @@ snapshots: '@types/express-serve-static-core': 5.1.0 '@types/serve-static': 1.15.10 + '@types/fs-extra@9.0.13': + dependencies: + '@types/node': 24.10.1 + + '@types/http-cache-semantics@4.2.0': {} + '@types/http-errors@2.0.5': {} '@types/json-schema@7.0.15': {} @@ -5948,6 +7104,10 @@ snapshots: '@types/ms': 2.1.0 '@types/node': 24.10.1 + '@types/keyv@3.1.4': + dependencies: + '@types/node': 24.10.1 + '@types/mime@1.3.5': {} '@types/ms@2.1.0': {} @@ -5968,6 +7128,12 @@ snapshots: dependencies: '@types/node': 24.10.1 + '@types/plist@3.0.5': + dependencies: + '@types/node': 24.10.1 + xmlbuilder: 15.1.1 + optional: true + '@types/qs@6.14.0': {} '@types/range-parser@1.2.7': {} @@ -5980,6 +7146,10 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/responselike@1.0.3': + dependencies: + '@types/node': 24.10.1 + '@types/send@0.17.6': dependencies: '@types/mime': 1.3.5 @@ -6007,6 +7177,14 @@ snapshots: '@types/strip-json-comments@0.0.30': {} + '@types/verror@1.10.11': + optional: true + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 24.10.1 + optional: true + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -6223,6 +7401,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@xmldom/xmldom@0.8.11': {} + + abbrev@1.1.1: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -6243,14 +7425,44 @@ snapshots: acorn@8.15.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + agent-base@7.1.4: {} - ajv@6.12.6: + agentkeepalive@4.6.0: dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 + humanize-ms: 1.2.1 + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv-formats@3.0.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-keywords@3.5.2(ajv@6.12.6): + dependencies: + ajv: 6.12.6 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 ansi-regex@5.0.1: {} @@ -6269,8 +7481,93 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + app-builder-bin@5.0.0-alpha.10: {} + + app-builder-lib@25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8): + dependencies: + '@develar/schema-utils': 2.6.5 + '@electron/notarize': 2.5.0 + '@electron/osx-sign': 1.3.1 + '@electron/rebuild': 3.6.1 + '@electron/universal': 2.0.1 + '@malept/flatpak-bundler': 0.4.0 + '@types/fs-extra': 9.0.13 + async-exit-hook: 2.0.1 + bluebird-lst: 1.0.9 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chromium-pickle-js: 0.2.0 + config-file-ts: 0.2.8-rc1 + debug: 4.4.3 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + dotenv: 16.6.1 + dotenv-expand: 11.0.7 + ejs: 3.1.10 + electron-builder-squirrel-windows: 25.1.8(dmg-builder@25.1.8) + electron-publish: 25.1.7 + form-data: 4.0.5 + fs-extra: 10.1.0 + hosted-git-info: 4.1.0 + is-ci: 3.0.1 + isbinaryfile: 5.0.7 + js-yaml: 4.1.1 + json5: 2.2.3 + lazy-val: 1.0.5 + minimatch: 10.2.4 + resedit: 1.7.2 + sanitize-filename: 1.6.4 + semver: 7.7.3 + tar: 6.2.1 + temp-file: 3.4.0 + transitivePeerDependencies: + - bluebird + - supports-color + append-field@1.0.0: {} + aproba@2.1.0: {} + + archiver-utils@2.1.0: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 2.3.8 + + archiver-utils@3.0.4: + dependencies: + glob: 7.2.3 + graceful-fs: 4.2.11 + lazystream: 1.0.1 + lodash.defaults: 4.2.0 + lodash.difference: 4.5.0 + lodash.flatten: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.union: 4.6.0 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + + archiver@5.3.2: + dependencies: + archiver-utils: 2.1.0 + async: 3.2.6 + buffer-crc32: 0.2.13 + readable-stream: 3.6.2 + readdir-glob: 1.1.3 + tar-stream: 2.2.0 + zip-stream: 4.1.1 + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + arg@4.1.3: {} arg@5.0.2: {} @@ -6350,10 +7647,29 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + assert-plus@1.0.0: + optional: true + ast-types-flow@0.0.8: {} + astral-regex@2.0.0: + optional: true + + async-exit-hook@2.0.1: {} + async-function@1.0.0: {} + async@3.2.6: {} + + asynckit@0.4.0: {} + + at-least-node@1.0.0: {} + + atomically@2.1.1: + dependencies: + stubborn-fs: 2.0.0 + when-exit: 2.1.5 + autoprefixer@10.4.22(postcss@8.5.6): dependencies: browserslist: 4.28.0 @@ -6370,10 +7686,20 @@ snapshots: axe-core@4.11.0: {} + axios@1.13.6: + dependencies: + follow-redirects: 1.15.11 + form-data: 4.0.5 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + axobject-query@4.1.0: {} balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} base64id@2.0.0: {} @@ -6391,6 +7717,18 @@ snapshots: binary-extensions@2.3.0: {} + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + bluebird-lst@1.0.9: + dependencies: + bluebird: 3.7.2 + + bluebird@3.7.2: {} + body-parser@2.2.0: dependencies: bytes: 3.1.2 @@ -6405,6 +7743,9 @@ snapshots: transitivePeerDependencies: - supports-color + boolean@3.2.0: + optional: true + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -6414,6 +7755,10 @@ snapshots: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.4: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -6426,10 +7771,45 @@ snapshots: node-releases: 2.0.27 update-browserslist-db: 1.1.4(browserslist@4.28.0) + buffer-crc32@0.2.13: {} + buffer-equal-constant-time@1.0.1: {} buffer-from@1.1.2: {} + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + builder-util-runtime@9.2.10: + dependencies: + debug: 4.4.3 + sax: 1.6.0 + transitivePeerDependencies: + - supports-color + + builder-util@25.1.7: + dependencies: + 7zip-bin: 5.2.0 + '@types/debug': 4.1.13 + app-builder-bin: 5.0.0-alpha.10 + bluebird-lst: 1.0.9 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + fs-extra: 10.1.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-ci: 3.0.1 + js-yaml: 4.1.1 + source-map-support: 0.5.21 + stat-mode: 1.0.0 + temp-file: 3.4.0 + transitivePeerDependencies: + - supports-color + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -6451,6 +7831,41 @@ snapshots: pkg-types: 2.3.0 rc9: 2.1.2 + cacache@16.1.3: + dependencies: + '@npmcli/fs': 2.1.2 + '@npmcli/move-file': 2.0.1 + chownr: 2.0.0 + fs-minipass: 2.1.0 + glob: 8.1.0 + infer-owner: 1.0.4 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + mkdirp: 1.0.4 + p-map: 4.0.0 + promise-inflight: 1.0.1 + rimraf: 3.0.2 + ssri: 9.0.1 + tar: 6.2.1 + unique-filename: 2.0.1 + transitivePeerDependencies: + - bluebird + + cacheable-lookup@5.0.4: {} + + cacheable-request@7.0.4: + dependencies: + clone-response: 1.0.3 + get-stream: 5.2.0 + http-cache-semantics: 4.2.0 + keyv: 4.5.4 + lowercase-keys: 2.0.0 + normalize-url: 6.1.0 + responselike: 2.0.1 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -6504,6 +7919,12 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@2.0.0: {} + + chromium-pickle-js@0.2.0: {} + + ci-info@3.9.0: {} + citty@0.1.6: dependencies: consola: 3.4.2 @@ -6512,6 +7933,20 @@ snapshots: dependencies: clsx: 2.1.1 + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cli-truncate@2.1.0: + dependencies: + slice-ansi: 3.0.0 + string-width: 4.2.3 + optional: true + client-only@0.0.1: {} cliui@8.0.1: @@ -6520,6 +7955,12 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clone-response@1.0.3: + dependencies: + mimic-response: 1.0.1 + + clone@1.0.4: {} + clsx@2.1.1: {} color-convert@2.0.1: @@ -6528,8 +7969,25 @@ snapshots: color-name@1.1.4: {} + color-support@1.1.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + commander@4.1.1: {} + commander@5.1.0: {} + + compare-version@0.1.2: {} + + compress-commons@4.1.2: + dependencies: + buffer-crc32: 0.2.13 + crc32-stream: 4.0.3 + normalize-path: 3.0.0 + readable-stream: 3.6.2 + concat-map@0.0.1: {} concat-stream@2.0.0: @@ -6551,10 +8009,29 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 + conf@14.0.0: + dependencies: + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) + atomically: 2.1.1 + debounce-fn: 6.0.0 + dot-prop: 9.0.0 + env-paths: 3.0.0 + json-schema-typed: 8.0.2 + semver: 7.7.3 + uint8array-extras: 1.5.0 + confbox@0.2.2: {} + config-file-ts@0.2.8-rc1: + dependencies: + glob: 10.5.0 + typescript: 5.9.3 + consola@3.4.2: {} + console-control-strings@1.1.0: {} + content-disposition@1.0.1: {} content-type@1.0.5: {} @@ -6574,11 +8051,28 @@ snapshots: cookie@1.1.1: {} + core-util-is@1.0.2: + optional: true + + core-util-is@1.0.3: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 vary: 1.1.2 + crc-32@1.2.2: {} + + crc32-stream@4.0.3: + dependencies: + crc-32: 1.2.2 + readable-stream: 3.6.2 + + crc@3.8.0: + dependencies: + buffer: 5.7.1 + optional: true + create-require@1.1.1: {} cross-spawn@7.0.6: @@ -6617,6 +8111,10 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 + debounce-fn@6.0.0: + dependencies: + mimic-function: 5.0.1 + debug@3.2.7: dependencies: ms: 2.1.3 @@ -6629,10 +8127,20 @@ snapshots: dependencies: ms: 2.1.3 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deep-is@0.1.4: {} deepmerge-ts@7.1.5: {} + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + defer-to-connect@2.0.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -6647,6 +8155,10 @@ snapshots: defu@6.1.4: {} + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + depd@2.0.0: {} destr@2.0.5: {} @@ -6655,16 +8167,59 @@ snapshots: detect-node-es@1.1.0: {} + detect-node@2.1.0: + optional: true + didyoumean@1.2.2: {} diff@4.0.2: {} + dir-compare@4.2.0: + dependencies: + minimatch: 3.1.2 + p-limit: 3.1.0 + dlv@1.1.3: {} + dmg-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + fs-extra: 10.1.0 + iconv-lite: 0.6.3 + js-yaml: 4.1.1 + optionalDependencies: + dmg-license: 1.0.11 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + dmg-license@1.0.11: + dependencies: + '@types/plist': 3.0.5 + '@types/verror': 1.10.11 + ajv: 6.12.6 + crc: 3.8.0 + iconv-corefoundation: 1.1.7 + plist: 3.1.0 + smart-buffer: 4.2.0 + verror: 1.10.1 + optional: true + doctrine@2.1.0: dependencies: esutils: 2.0.3 + dot-prop@9.0.0: + dependencies: + type-fest: 4.41.0 + + dotenv-expand@11.0.7: + dependencies: + dotenv: 16.6.1 + dotenv@16.6.1: {} dotenv@17.2.3: {} @@ -6692,8 +8247,67 @@ snapshots: '@standard-schema/spec': 1.0.0 fast-check: 3.23.2 + ejs@3.1.10: + dependencies: + jake: 10.9.4 + + electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + archiver: 5.3.2 + builder-util: 25.1.7 + fs-extra: 10.1.0 + transitivePeerDependencies: + - bluebird + - dmg-builder + - supports-color + + electron-builder@25.1.8(electron-builder-squirrel-windows@25.1.8): + dependencies: + app-builder-lib: 25.1.8(dmg-builder@25.1.8)(electron-builder-squirrel-windows@25.1.8) + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + dmg-builder: 25.1.8(electron-builder-squirrel-windows@25.1.8) + fs-extra: 10.1.0 + is-ci: 3.0.1 + lazy-val: 1.0.5 + simple-update-notifier: 2.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - bluebird + - electron-builder-squirrel-windows + - supports-color + + electron-is-dev@3.0.1: {} + + electron-publish@25.1.7: + dependencies: + '@types/fs-extra': 9.0.13 + builder-util: 25.1.7 + builder-util-runtime: 9.2.10 + chalk: 4.1.2 + fs-extra: 10.1.0 + lazy-val: 1.0.5 + mime: 2.6.0 + transitivePeerDependencies: + - supports-color + + electron-store@10.1.0: + dependencies: + conf: 14.0.0 + type-fest: 4.41.0 + electron-to-chromium@1.5.256: {} + electron@34.5.8: + dependencies: + '@electron/get': 2.0.3 + '@types/node': 20.19.33 + extract-zip: 2.0.1 + transitivePeerDependencies: + - supports-color + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -6702,6 +8316,15 @@ snapshots: encodeurl@2.0.0: {} + encoding@0.1.13: + dependencies: + iconv-lite: 0.6.3 + optional: true + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + engine.io-client@6.6.3: dependencies: '@socket.io/component-emitter': 3.1.2 @@ -6737,6 +8360,12 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.0 + env-paths@2.2.1: {} + + env-paths@3.0.0: {} + + err-code@2.0.3: {} + es-abstract@1.24.0: dependencies: array-buffer-byte-length: 1.0.2 @@ -6838,6 +8467,9 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + es6-error@4.1.1: + optional: true + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -7169,6 +8801,8 @@ snapshots: events@3.3.0: {} + exponential-backoff@3.1.3: {} + express@5.1.0: dependencies: accepts: 2.0.0 @@ -7205,6 +8839,19 @@ snapshots: extend@3.0.2: {} + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + extsprintf@1.4.1: + optional: true + fast-check@3.23.2: dependencies: pure-rand: 6.1.0 @@ -7231,10 +8878,16 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-uri@3.1.0: {} + fastq@1.19.1: dependencies: reusify: 1.1.0 + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + fdir@6.5.0(picomatch@4.0.3): optionalDependencies: picomatch: 4.0.3 @@ -7248,6 +8901,10 @@ snapshots: dependencies: flat-cache: 4.0.1 + filelist@1.0.6: + dependencies: + minimatch: 5.1.9 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -7275,6 +8932,8 @@ snapshots: flatted@3.3.3: {} + follow-redirects@1.15.11: {} + for-each@0.3.5: dependencies: is-callable: 1.2.7 @@ -7284,6 +8943,14 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -7303,6 +8970,37 @@ snapshots: fresh@2.0.0: {} + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@11.3.4: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs-extra@9.1.0: + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -7321,6 +9019,17 @@ snapshots: functions-have-names@1.2.3: {} + gauge@4.0.4: + dependencies: + aproba: 2.1.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + gaxios@7.1.3: dependencies: extend: 3.0.2 @@ -7364,6 +9073,10 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + get-symbol-description@1.1.0: dependencies: call-bound: 1.0.4 @@ -7409,6 +9122,24 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + global-agent@3.0.0: + dependencies: + boolean: 3.2.0 + es6-error: 4.1.1 + matcher: 3.0.0 + roarr: 2.15.4 + semver: 7.7.3 + serialize-error: 7.0.1 + optional: true + globals@14.0.0: {} globals@16.4.0: {} @@ -7437,6 +9168,20 @@ snapshots: gopd@1.2.0: {} + got@11.8.6: + dependencies: + '@sindresorhus/is': 4.6.0 + '@szmarczak/http-timer': 4.0.6 + '@types/cacheable-request': 6.0.3 + '@types/responselike': 1.0.3 + cacheable-lookup: 5.0.4 + cacheable-request: 7.0.4 + decompress-response: 6.0.0 + http2-wrapper: 1.0.3 + lowercase-keys: 2.0.0 + p-cancelable: 2.1.1 + responselike: 2.0.1 + graceful-fs@4.2.11: {} graphemer@1.4.0: {} @@ -7459,23 +9204,58 @@ snapshots: dependencies: has-symbols: 1.1.0 + has-unicode@2.0.1: {} + hasown@2.0.2: dependencies: function-bind: 1.1.2 hermes-estree@0.25.1: {} - hermes-parser@0.25.1: + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hosted-git-info@4.1.0: + dependencies: + lru-cache: 6.0.0 + + http-cache-semantics@4.2.0: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.0 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http2-wrapper@1.0.3: dependencies: - hermes-estree: 0.25.1 + quick-lru: 5.1.1 + resolve-alpn: 1.2.1 - http-errors@2.0.0: + https-proxy-agent@5.0.1: dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color https-proxy-agent@7.0.6: dependencies: @@ -7484,6 +9264,16 @@ snapshots: transitivePeerDependencies: - supports-color + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + + iconv-corefoundation@1.1.7: + dependencies: + cli-truncate: 2.1.0 + node-addon-api: 1.7.2 + optional: true + iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 @@ -7492,6 +9282,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} ignore@7.0.5: {} @@ -7503,6 +9295,10 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + + infer-owner@1.0.4: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -7518,6 +9314,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + ip-address@10.1.0: {} + ipaddr.js@1.9.1: {} is-array-buffer@3.0.5: @@ -7553,6 +9351,10 @@ snapshots: is-callable@1.2.7: {} + is-ci@3.0.1: + dependencies: + ci-info: 3.9.0 + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -7588,6 +9390,10 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-interactive@1.0.0: {} + + is-lambda@1.0.1: {} + is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -7629,6 +9435,8 @@ snapshots: dependencies: which-typed-array: 1.1.19 + is-unicode-supported@0.1.0: {} + is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -7640,8 +9448,14 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + isarray@1.0.0: {} + isarray@2.0.5: {} + isbinaryfile@4.0.10: {} + + isbinaryfile@5.0.7: {} + isexe@2.0.0: {} iterator.prototype@1.1.5: @@ -7659,10 +9473,26 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 + jake@10.9.4: + dependencies: + async: 3.2.6 + filelist: 1.0.6 + picocolors: 1.1.1 + jiti@1.21.7: {} jiti@2.6.1: {} + joi@18.0.2: + dependencies: + '@hapi/address': 5.1.1 + '@hapi/formula': 3.0.2 + '@hapi/hoek': 11.0.7 + '@hapi/pinpoint': 2.0.1 + '@hapi/tlds': 1.1.6 + '@hapi/topo': 6.0.2 + '@standard-schema/spec': 1.0.0 + jose@5.10.0: {} jose@6.2.1: {} @@ -7683,14 +9513,31 @@ snapshots: json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: + optional: true + json5@1.0.2: dependencies: minimist: 1.2.8 json5@2.2.3: {} + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -7743,6 +9590,12 @@ snapshots: dependencies: language-subtag-registry: 0.3.23 + lazy-val@1.0.5: {} + + lazystream@1.0.1: + dependencies: + readable-stream: 2.3.8 + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -7825,6 +9678,12 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.defaults@4.2.0: {} + + lodash.difference@4.5.0: {} + + lodash.flatten@4.4.0: {} + lodash.includes@4.3.0: {} lodash.isboolean@3.0.3: {} @@ -7841,20 +9700,37 @@ snapshots: lodash.once@4.1.1: {} + lodash.union@4.6.0: {} + lodash@4.17.21: {} + lodash@4.17.23: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + loglevel@1.9.2: {} loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 + lowercase-keys@2.0.0: {} + lru-cache@10.4.3: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 + lru-cache@6.0.0: + dependencies: + yallist: 4.0.0 + + lru-cache@7.18.3: {} + lucide-react@0.553.0(react@19.2.0): dependencies: react: 19.2.0 @@ -7865,8 +9741,35 @@ snapshots: make-error@1.3.6: {} + make-fetch-happen@10.2.1: + dependencies: + agentkeepalive: 4.6.0 + cacache: 16.1.3 + http-cache-semantics: 4.2.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-lambda: 1.0.1 + lru-cache: 7.18.3 + minipass: 3.3.6 + minipass-collect: 1.0.2 + minipass-fetch: 2.1.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + socks-proxy-agent: 7.0.0 + ssri: 9.0.1 + transitivePeerDependencies: + - bluebird + - supports-color + map-obj@5.0.0: {} + matcher@3.0.0: + dependencies: + escape-string-regexp: 4.0.0 + optional: true + math-intrinsics@1.1.0: {} media-typer@0.3.0: {} @@ -7894,18 +9797,71 @@ snapshots: dependencies: mime-db: 1.54.0 + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + mimic-function@5.0.1: {} + + mimic-response@1.0.1: {} + + mimic-response@3.1.0: {} + + minimatch@10.2.4: + dependencies: + brace-expansion: 5.0.4 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.12 + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.2 + minimatch@9.0.5: dependencies: brace-expansion: 2.0.2 minimist@1.2.8: {} + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-fetch@2.1.2: + dependencies: + minipass: 3.3.6 + minipass-sized: 1.0.3 + minizlib: 2.1.2 + optionalDependencies: + encoding: 0.1.13 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass-sized@1.0.3: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + minipass@7.1.2: {} + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + mkdirp@1.0.4: {} motion-dom@12.24.11: @@ -7967,8 +9923,19 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-abi@3.89.0: + dependencies: + semver: 7.7.3 + + node-addon-api@1.7.2: + optional: true + node-addon-api@8.5.0: {} + node-api-version@0.2.1: + dependencies: + semver: 7.7.3 + node-domexception@1.0.0: {} node-fetch-native@1.6.7: {} @@ -7981,14 +9948,44 @@ snapshots: node-gyp-build@4.8.4: {} + node-gyp@9.4.1: + dependencies: + env-paths: 2.2.1 + exponential-backoff: 3.1.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + make-fetch-happen: 10.2.1 + nopt: 6.0.0 + npmlog: 6.0.2 + rimraf: 3.0.2 + semver: 7.7.3 + tar: 6.2.1 + which: 2.0.2 + transitivePeerDependencies: + - bluebird + - supports-color + node-releases@2.0.27: {} nodemailer@7.0.12: {} + nopt@6.0.0: + dependencies: + abbrev: 1.1.1 + normalize-path@3.0.0: {} normalize-range@0.1.2: {} + normalize-url@6.1.0: {} + + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + nypm@0.6.2: dependencies: citty: 0.1.6 @@ -8051,6 +10048,10 @@ snapshots: dependencies: wrappy: 1.0.2 + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -8060,12 +10061,26 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + own-keys@1.0.1: dependencies: get-intrinsic: 1.3.0 object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-cancelable@2.1.1: {} + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 @@ -8074,6 +10089,10 @@ snapshots: dependencies: p-limit: 3.1.0 + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + package-json-from-dist@1.0.1: {} parent-module@1.0.1: @@ -8104,6 +10123,10 @@ snapshots: pathe@2.0.3: {} + pe-library@0.4.1: {} + + pend@1.2.0: {} + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -8122,6 +10145,12 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 + plist@3.1.0: + dependencies: + '@xmldom/xmldom': 0.8.11 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + possible-typed-array-names@1.1.0: {} postcss-import@15.1.0(postcss@8.5.6): @@ -8179,8 +10208,19 @@ snapshots: transitivePeerDependencies: - magicast + process-nextick-args@2.0.1: {} + process@0.11.10: {} + progress@2.0.3: {} + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -8192,6 +10232,13 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + punycode@1.4.1: {} punycode@2.3.1: {} @@ -8204,6 +10251,8 @@ snapshots: queue-microtask@1.2.3: {} + quick-lru@5.1.1: {} + quick-lru@6.1.2: {} range-parser@1.2.1: {} @@ -8262,16 +10311,36 @@ snapshots: react@19.2.0: {} + read-binary-file-arch@1.0.6: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + read-cache@1.0.0: dependencies: pify: 2.3.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 + readdir-glob@1.1.3: + dependencies: + minimatch: 5.1.9 + readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -8300,6 +10369,14 @@ snapshots: require-directory@2.1.1: {} + require-from-string@2.0.2: {} + + resedit@1.7.2: + dependencies: + pe-library: 0.4.1 + + resolve-alpn@1.2.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -8316,16 +10393,41 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + responselike@2.0.1: + dependencies: + lowercase-keys: 2.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.12.0: {} + reusify@1.1.0: {} rimraf@2.7.1: dependencies: glob: 7.2.3 + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + rimraf@5.0.10: dependencies: glob: 10.5.0 + roarr@2.15.4: + dependencies: + boolean: 3.2.0 + detect-node: 2.1.0 + globalthis: 1.0.4 + json-stringify-safe: 5.0.1 + semver-compare: 1.0.0 + sprintf-js: 1.1.3 + optional: true + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -8380,6 +10482,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-push-apply@1.0.0: @@ -8395,12 +10499,21 @@ snapshots: safer-buffer@2.1.2: {} + sanitize-filename@1.6.4: + dependencies: + truncate-utf8-bytes: 1.0.2 + + sax@1.6.0: {} + scheduler@0.27.0: {} sdp-transform@2.15.0: {} sdp@3.2.1: {} + semver-compare@1.0.0: + optional: true + semver@6.3.1: {} semver@7.7.3: {} @@ -8421,6 +10534,11 @@ snapshots: transitivePeerDependencies: - supports-color + serialize-error@7.0.1: + dependencies: + type-fest: 0.13.1 + optional: true + serve-static@2.2.0: dependencies: encodeurl: 2.0.0 @@ -8430,6 +10548,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-blocking@2.0.0: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -8522,8 +10642,23 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signal-exit@3.0.7: {} + signal-exit@4.1.0: {} + simple-update-notifier@2.0.0: + dependencies: + semver: 7.7.3 + + slice-ansi@3.0.0: + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + optional: true + + smart-buffer@4.2.0: {} + socket.io-adapter@2.5.5: dependencies: debug: 4.3.7 @@ -8565,6 +10700,19 @@ snapshots: - supports-color - utf-8-validate + socks-proxy-agent@7.0.0: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + socks: 2.8.7 + transitivePeerDependencies: + - supports-color + + socks@2.8.7: + dependencies: + ip-address: 10.1.0 + smart-buffer: 4.2.0 + sonner@2.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 @@ -8581,8 +10729,17 @@ snapshots: spawn-command@0.0.2: {} + sprintf-js@1.1.3: + optional: true + + ssri@9.0.1: + dependencies: + minipass: 3.3.6 + stable-hash@0.0.5: {} + stat-mode@1.0.0: {} + statuses@2.0.1: {} statuses@2.0.2: {} @@ -8656,6 +10813,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -8674,6 +10835,12 @@ snapshots: strip-json-comments@3.1.1: {} + stubborn-fs@2.0.0: + dependencies: + stubborn-utils: 1.0.2 + + stubborn-utils@1.0.2: {} + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): dependencies: client-only: 0.0.1 @@ -8691,6 +10858,12 @@ snapshots: pirates: 4.0.7 ts-interface-checker: 0.1.13 + sumchecker@3.0.1: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8741,6 +10914,28 @@ snapshots: tapable@2.3.0: {} + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + temp-file@3.4.0: + dependencies: + async-exit-hook: 2.0.1 + fs-extra: 10.1.0 + thenify-all@1.6.0: dependencies: thenify: 3.3.1 @@ -8756,6 +10951,12 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 + tmp-promise@3.0.3: + dependencies: + tmp: 0.2.5 + + tmp@0.2.5: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -8764,6 +10965,10 @@ snapshots: tree-kill@1.2.2: {} + truncate-utf8-bytes@1.0.2: + dependencies: + utf8-byte-length: 1.0.5 + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -8839,6 +11044,9 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.13.1: + optional: true + type-fest@4.41.0: {} type-is@1.6.18: @@ -8915,6 +11123,8 @@ snapshots: typescript@5.9.3: {} + uint8array-extras@1.5.0: {} + unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -8926,6 +11136,18 @@ snapshots: undici-types@7.16.0: {} + unique-filename@2.0.1: + dependencies: + unique-slug: 3.0.0 + + unique-slug@3.0.0: + dependencies: + imurmurhash: 0.1.4 + + universalify@0.1.2: {} + + universalify@2.0.1: {} + unpipe@1.0.0: {} unrs-resolver@1.11.1: @@ -8986,6 +11208,8 @@ snapshots: dependencies: react: 19.2.0 + utf8-byte-length@1.0.5: {} + util-deprecate@1.0.2: {} util@0.10.4: @@ -8996,6 +11220,13 @@ snapshots: vary@1.1.2: {} + verror@1.10.1: + dependencies: + assert-plus: 1.0.0 + core-util-is: 1.0.2 + extsprintf: 1.4.1 + optional: true + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)): dependencies: debug: 4.4.3 @@ -9022,12 +11253,28 @@ snapshots: lightningcss: 1.30.2 tsx: 4.21.0 + wait-on@9.0.4: + dependencies: + axios: 1.13.6 + joi: 18.0.2 + lodash: 4.17.23 + minimist: 1.2.8 + rxjs: 7.8.2 + transitivePeerDependencies: + - debug + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + web-streams-polyfill@3.3.3: {} webrtc-adapter@9.0.4: dependencies: sdp: 3.2.1 + when-exit@2.1.5: {} + which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 @@ -9073,6 +11320,10 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -9091,6 +11342,8 @@ snapshots: ws@8.17.1: {} + xmlbuilder@15.1.1: {} + xmlhttprequest-ssl@2.1.2: {} xtend@4.0.2: {} @@ -9099,6 +11352,8 @@ snapshots: yallist@3.1.1: {} + yallist@4.0.0: {} + yargs-parser@21.1.1: {} yargs@17.7.2: @@ -9111,10 +11366,21 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + yn@3.1.1: {} yocto-queue@0.1.0: {} + zip-stream@4.1.1: + dependencies: + archiver-utils: 3.0.4 + compress-commons: 4.1.2 + readable-stream: 3.6.2 + zod-validation-error@4.0.2(zod@4.3.6): dependencies: zod: 4.3.6 From 9b89ffbf10c5b180c792f6e9c918b6081b670a9d Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 17:54:53 +0200 Subject: [PATCH 02/19] feat(desktop): run Next app in Electron --- .gitignore | 6 + apps/frontend/next.config.js | 1 + apps/frontend/package.json | 2 +- desktop/electron-builder.json | 3 +- desktop/main.ts | 201 +++++++++++++++++++++++---- desktop/package.json | 13 +- desktop/preload.cjs | 14 ++ desktop/scripts/prepare-renderer.mjs | 66 +++++++++ desktop/tsconfig.json | 14 ++ package.json | 2 +- pnpm-lock.yaml | 140 ------------------- pnpm-workspace.yaml | 1 + 12 files changed, 288 insertions(+), 175 deletions(-) create mode 100644 desktop/preload.cjs create mode 100644 desktop/scripts/prepare-renderer.mjs create mode 100644 desktop/tsconfig.json diff --git a/.gitignore b/.gitignore index 8b155c0..5fc16a0 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,9 @@ apps/*/.next apps/*/dist apps/*/.turbo +# Desktop build artifacts +desktop/renderer/ + +# Codex local metadata +.codex +.codex/ diff --git a/apps/frontend/next.config.js b/apps/frontend/next.config.js index 85aa045..e76db56 100644 --- a/apps/frontend/next.config.js +++ b/apps/frontend/next.config.js @@ -34,6 +34,7 @@ const nextConfig = { poweredByHeader: false, compress: true, reactStrictMode: true, + output: 'standalone', async headers() { return [ diff --git a/apps/frontend/package.json b/apps/frontend/package.json index b95bed8..cbbdc43 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -4,7 +4,7 @@ "type": "module", "scripts": { "dev": "next dev", - "build": "next build", + "build": "next build --webpack", "start": "next start", "lint": "eslint ." }, diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index e627261..705a4a5 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -1,11 +1,12 @@ { "appId": "com.openchat.app", "productName": "OpenChat", + "asarUnpack": ["renderer/**"], "directories": { "output": "dist", "buildResources": "assets" }, - "files": ["dist/**/*", "package.json", "icon.png"], + "files": ["build/**/*", "renderer/**", "package.json", "preload.cjs", "icon.png"], "mac": { "category": "public.app-category.social-networking", "target": ["dmg", "zip"], diff --git a/desktop/main.ts b/desktop/main.ts index f6dd67d..ae5993e 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1,19 +1,159 @@ -import { app, BrowserWindow, shell, Menu } from "electron"; +import { app, BrowserWindow, shell, Menu, dialog } from "electron"; import path from "path"; -import isDev from "electron-is-dev"; +import fs from "fs"; +import net from "net"; +import { spawn, type ChildProcessWithoutNullStreams } from "child_process"; -import Store from "electron-store"; - -const store = new Store(); +const isDev = !app.isPackaged; let mainWindow: BrowserWindow | null = null; +let nextServerProcess: ChildProcessWithoutNullStreams | null = null; +let nextServerPort: number | null = null; + +type WindowState = { width: number; height: number; x?: number; y?: number }; +const defaultWindowState: WindowState = { width: 1200, height: 800 }; + +function getWindowStatePath(): string { + return path.join(app.getPath("userData"), "window-state.json"); +} + +function readWindowState(): WindowState { + try { + const raw = fs.readFileSync(getWindowStatePath(), "utf8"); + const parsed = JSON.parse(raw) as WindowState; + return { + width: typeof parsed.width === "number" ? parsed.width : defaultWindowState.width, + height: typeof parsed.height === "number" ? parsed.height : defaultWindowState.height, + x: typeof parsed.x === "number" ? parsed.x : undefined, + y: typeof parsed.y === "number" ? parsed.y : undefined, + }; + } catch { + return { ...defaultWindowState }; + } +} -function createWindow() { - const windowState = store.get("windowState", { - width: 1200, - height: 800, - x: undefined, - y: undefined, - }) as { width: number; height: number; x: number | undefined; y: number | undefined }; +function writeWindowState(bounds: { width: number; height: number; x: number; y: number }) { + try { + const state: WindowState = { width: bounds.width, height: bounds.height, x: bounds.x, y: bounds.y }; + fs.writeFileSync(getWindowStatePath(), JSON.stringify(state), "utf8"); + } catch { + // Best-effort persistence only. + } +} + +function resolveAppFile(relPath: string): string { + const candidates = [ + path.join(app.getAppPath(), relPath), + path.join(__dirname, relPath), + path.join(__dirname, "..", relPath), + ]; + + for (const candidate of candidates) { + if (fs.existsSync(candidate)) return candidate; + } + + return candidates[0]; +} + +function isPortFree(port: number): Promise { + return new Promise((resolve) => { + const tester = net.createServer(); + tester.unref(); + tester.on("error", () => resolve(false)); + tester.listen(port, "127.0.0.1", () => { + tester.close(() => resolve(true)); + }); + }); +} + +async function pickPort(startPort = 3000, maxTries = 50): Promise { + for (let i = 0; i < maxTries; i++) { + const port = startPort + i; + // eslint-disable-next-line no-await-in-loop + if (await isPortFree(port)) return port; + } + throw new Error(`Could not find a free port starting at ${startPort}.`); +} + +function waitForTcp(host: string, port: number, timeoutMs = 20_000): Promise { + const start = Date.now(); + + return new Promise((resolve, reject) => { + const tryOnce = () => { + const socket = net.connect({ host, port }); + socket.once("connect", () => { + socket.end(); + resolve(); + }); + socket.once("error", () => { + socket.destroy(); + if (Date.now() - start > timeoutMs) { + reject(new Error(`Timed out waiting for ${host}:${port}`)); + return; + } + setTimeout(tryOnce, 200); + }); + }; + + tryOnce(); + }); +} + +async function ensureNextServerStarted(): Promise { + if (nextServerPort !== null) return nextServerPort; + + const rendererDirCandidates = [ + path.join(app.getAppPath(), "renderer"), + path.join(process.resourcesPath, "app.asar.unpacked", "renderer"), + ]; + const rendererDir = rendererDirCandidates.find((p) => fs.existsSync(p)) ?? rendererDirCandidates[0]; + const serverEntryCandidates = [ + path.join(rendererDir, "server.js"), + path.join(rendererDir, "apps", "frontend", "server.js"), + ]; + const serverEntry = serverEntryCandidates.find((p) => fs.existsSync(p)); + if (!serverEntry) { + throw new Error( + `Missing Next server entry under ${rendererDir}. Build the desktop app first (pnpm build:desktop).`, + ); + } + + nextServerPort = await pickPort(3000); + + nextServerProcess = spawn( + process.execPath, + ["--runAsNode", serverEntry], + { + cwd: rendererDir, + env: { + ...process.env, + NODE_ENV: "production", + HOSTNAME: "127.0.0.1", + PORT: String(nextServerPort), + }, + stdio: "pipe", + }, + ); + + nextServerProcess.on("exit", (code, signal) => { + if (isDev) return; + console.error(`Next server exited (code=${code}, signal=${signal})`); + }); + + nextServerProcess.stdout.on("data", (buf) => console.log(String(buf).trimEnd())); + nextServerProcess.stderr.on("data", (buf) => console.error(String(buf).trimEnd())); + + await waitForTcp("127.0.0.1", nextServerPort); + return nextServerPort; +} + +async function resolveAppUrl(): Promise { + if (isDev) return "http://localhost:3000"; + const port = await ensureNextServerStarted(); + return `http://127.0.0.1:${port}`; +} + +async function createWindow() { + const windowState = readWindowState(); mainWindow = new BrowserWindow({ width: windowState.width, @@ -22,33 +162,38 @@ function createWindow() { y: windowState.y, minWidth: 800, minHeight: 600, - titleBarStyle: "hidden", - trafficLightPosition: { x: 15, y: 15 }, + ...(process.platform === "darwin" + ? { titleBarStyle: "hidden", trafficLightPosition: { x: 15, y: 15 } } + : {}), webPreferences: { nodeIntegration: false, contextIsolation: true, - preload: path.join(__dirname, "preload.js"), + preload: resolveAppFile("preload.cjs"), }, - icon: path.join(__dirname, "icon.png"), + icon: resolveAppFile("icon.png"), }); mainWindow.on("resize", () => { if (!mainWindow) return; const { width, height, x, y } = mainWindow.getBounds(); - store.set("windowState", { width, height, x, y }); + writeWindowState({ width, height, x, y }); }); mainWindow.on("move", () => { if (!mainWindow) return; const { width, height, x, y } = mainWindow.getBounds(); - store.set("windowState", { width, height, x, y }); + writeWindowState({ width, height, x, y }); }); - const url = isDev - ? "http://localhost:3000" - : `file://${path.join(__dirname, "../apps/frontend/out/index.html")}`; - - mainWindow.loadURL(url); + try { + const url = await resolveAppUrl(); + await mainWindow.loadURL(url); + } catch (err) { + const message = err instanceof Error ? err.message : String(err); + dialog.showErrorBox("OpenChat failed to start", message); + app.quit(); + return; + } // Open external links in default browser mainWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) => { @@ -100,7 +245,7 @@ function setAppMenu() { app.whenReady().then(() => { setAppMenu(); - createWindow(); + void createWindow(); }); app.on("window-all-closed", () => { @@ -111,6 +256,12 @@ app.on("window-all-closed", () => { app.on("activate", () => { if (mainWindow === null) { - createWindow(); + void createWindow(); + } +}); + +app.on("before-quit", () => { + if (nextServerProcess && !nextServerProcess.killed) { + nextServerProcess.kill(); } }); diff --git a/desktop/package.json b/desktop/package.json index 5521f79..d378d1e 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,16 +1,15 @@ { "name": "@openchat/desktop", "version": "1.0.0", - "main": "main.ts", + "main": "build/main.js", "scripts": { - "dev": "npx -p electron electron -r tsx/register .", - "build": "tsc", - "package": "npm run build && npx electron-builder", - "dist": "npm run build && npx electron-builder" + "dev": "pnpm run build && pnpm exec electron .", + "build": "tsc -p tsconfig.json", + "prepare:renderer": "node ./scripts/prepare-renderer.mjs", + "package": "pnpm run build && pnpm exec electron-builder", + "dist": "pnpm run build && pnpm exec electron-builder" }, "dependencies": { - "electron-is-dev": "^3.0.1", - "electron-store": "^10.0.0" }, "devDependencies": { "electron": "^34.5.8", diff --git a/desktop/preload.cjs b/desktop/preload.cjs new file mode 100644 index 0000000..af2150e --- /dev/null +++ b/desktop/preload.cjs @@ -0,0 +1,14 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +const fs = require("fs"); +const path = require("path"); + +// In production we ship compiled JS at ./build/preload.js +const compiledPreloadPath = path.join(__dirname, "build", "preload.js"); + +if (fs.existsSync(compiledPreloadPath)) { + require(compiledPreloadPath); +} else { + // Dev: allow loading the TypeScript preload via tsx + require("tsx/cjs"); + require(path.join(__dirname, "preload.ts")); +} diff --git a/desktop/scripts/prepare-renderer.mjs b/desktop/scripts/prepare-renderer.mjs new file mode 100644 index 0000000..59b53ed --- /dev/null +++ b/desktop/scripts/prepare-renderer.mjs @@ -0,0 +1,66 @@ +import path from "node:path"; +import fs from "node:fs/promises"; + +const repoRoot = path.resolve(import.meta.dirname, "..", ".."); +const desktopDir = path.resolve(import.meta.dirname, ".."); + +const frontendDir = path.join(repoRoot, "apps", "frontend"); +const nextDir = path.join(frontendDir, ".next"); + +const standaloneDir = path.join(nextDir, "standalone"); +const staticDir = path.join(nextDir, "static"); +const publicDir = path.join(frontendDir, "public"); + +const rendererDir = path.join(desktopDir, "renderer"); + +async function exists(p) { + try { + await fs.access(p); + return true; + } catch { + return false; + } +} + +async function main() { + if (!(await exists(standaloneDir))) { + throw new Error( + `Missing ${standaloneDir}. Run the frontend build first (pnpm --filter frontend build).`, + ); + } + + await fs.rm(rendererDir, { recursive: true, force: true }); + await fs.mkdir(rendererDir, { recursive: true }); + + // Copy the standalone server bundle (includes server.js + minimal node_modules) + await fs.cp(standaloneDir, rendererDir, { recursive: true }); + + const serverEntryCandidates = [ + path.join(rendererDir, "server.js"), + path.join(rendererDir, "apps", "frontend", "server.js"), + ]; + const serverEntry = await (async () => { + for (const candidate of serverEntryCandidates) { + // eslint-disable-next-line no-await-in-loop + if (await exists(candidate)) return candidate; + } + return null; + })(); + + if (!serverEntry) { + throw new Error(`Could not find server.js inside ${rendererDir}`); + } + + const serverBaseDir = path.dirname(serverEntry); + + if (await exists(staticDir)) { + await fs.mkdir(path.join(serverBaseDir, ".next"), { recursive: true }); + await fs.cp(staticDir, path.join(serverBaseDir, ".next", "static"), { recursive: true }); + } + + if (await exists(publicDir)) { + await fs.cp(publicDir, path.join(serverBaseDir, "public"), { recursive: true }); + } +} + +await main(); diff --git a/desktop/tsconfig.json b/desktop/tsconfig.json new file mode 100644 index 0000000..b23b808 --- /dev/null +++ b/desktop/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "CommonJS", + "moduleResolution": "Node", + "outDir": "build", + "rootDir": ".", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["main.ts", "preload.ts"] +} diff --git a/package.json b/package.json index b062283..3b65882 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dev:frontend": "pnpm --filter frontend dev", "dev:backend": "pnpm --filter backend dev", "dev:desktop": "concurrently \"pnpm dev:frontend\" \"wait-on http://localhost:3000 && pnpm --filter desktop dev\"", - "build:desktop": "pnpm --filter frontend build && pnpm --filter desktop build", + "build:desktop": "pnpm --filter frontend build && pnpm --filter desktop prepare:renderer && pnpm --filter desktop build", "dist:desktop": "pnpm build:desktop && pnpm --filter desktop dist", "dev": "concurrently \"pnpm dev:frontend\" \"pnpm dev:backend\"", "dev:all": "pnpm -w -r --parallel dev", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 435e874..29502bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -343,13 +343,6 @@ importers: version: 5.1.4(typescript@5.9.3)(vite@7.2.2(@types/node@24.10.1)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)) desktop: - dependencies: - electron-is-dev: - specifier: ^3.0.1 - version: 3.0.1 - electron-store: - specifier: ^10.0.0 - version: 10.1.0 devDependencies: electron: specifier: ^34.5.8 @@ -2326,14 +2319,6 @@ packages: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} - ajv-formats@3.0.1: - resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} - peerDependencies: - ajv: ^8.0.0 - peerDependenciesMeta: - ajv: - optional: true - ajv-keywords@3.5.2: resolution: {integrity: sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==} peerDependencies: @@ -2342,9 +2327,6 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.18.0: - resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} - ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -2479,9 +2461,6 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - atomically@2.1.1: - resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==} - autoprefixer@10.4.22: resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} engines: {node: ^10 || ^12 || >=14} @@ -2757,10 +2736,6 @@ packages: engines: {node: ^14.13.0 || >=16.0.0} hasBin: true - conf@14.0.0: - resolution: {integrity: sha512-L6BuueHTRuJHQvQVc6YXYZRtN5vJUtOdCTLn0tRYYV5azfbAFcPghB5zEE40mVrV6w7slMTqUfkDomutIK14fw==} - engines: {node: '>=20'} - confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} @@ -2864,10 +2839,6 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} - debounce-fn@6.0.0: - resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==} - engines: {node: '>=18'} - debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -2973,10 +2944,6 @@ packages: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} - dot-prop@9.0.0: - resolution: {integrity: sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==} - engines: {node: '>=18'} - dotenv-expand@11.0.7: resolution: {integrity: sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==} engines: {node: '>=12'} @@ -3021,17 +2988,9 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - electron-is-dev@3.0.1: - resolution: {integrity: sha512-8TjjAh8Ec51hUi3o4TaU0mD3GMTOESi866oRNavj9A3IQJ7pmv+MJVmdZBFGw4GFT36X7bkqnuDNYvkQgvyI8Q==} - engines: {node: '>=18'} - electron-publish@25.1.7: resolution: {integrity: sha512-+jbTkR9m39eDBMP4gfbqglDd6UvBC7RLh5Y0MhFSsc6UkGHj9Vj9TWobxevHYMMqmoujL11ZLjfPpMX+Pt6YEg==} - electron-store@10.1.0: - resolution: {integrity: sha512-oL8bRy7pVCLpwhmXy05Rh/L6O93+k9t6dqSw0+MckIc3OmCTZm6Mp04Q4f/J0rtu84Ky6ywkR8ivtGOmrq+16w==} - engines: {node: '>=20'} - electron-to-chromium@1.5.256: resolution: {integrity: sha512-uqYq1IQhpXXLX+HgiXdyOZml7spy4xfy42yPxcCCRjswp0fYM2X+JwCON07lqnpLEGVCj739B7Yr+FngmHBMEQ==} @@ -3079,10 +3038,6 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - env-paths@3.0.0: - resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} @@ -3314,9 +3269,6 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fast-uri@3.1.0: - resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} - fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -3900,12 +3852,6 @@ packages: json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-schema-traverse@1.0.0: - resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} - - json-schema-typed@8.0.2: - resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} - json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} @@ -4202,10 +4148,6 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -4790,10 +4732,6 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - require-from-string@2.0.2: - resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} - engines: {node: '>=0.10.0'} - resedit@1.7.2: resolution: {integrity: sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==} engines: {node: '>=12', npm: '>=6'} @@ -5126,12 +5064,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - stubborn-fs@2.0.0: - resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==} - - stubborn-utils@1.0.2: - resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==} - styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -5353,10 +5285,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - uint8array-extras@1.5.0: - resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} - engines: {node: '>=18'} - unbox-primitive@1.1.0: resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} engines: {node: '>= 0.4'} @@ -5512,9 +5440,6 @@ packages: resolution: {integrity: sha512-5ZZY1+lGq8LEKuDlg9M2RPJHlH3R7OVwyHqMcUsLKCgd9Wvf+QrFTCItkXXYPmrJn8H6gRLXbSgxLLdexiqHxw==} engines: {node: '>=6.0.0', npm: '>=3.10.0'} - when-exit@2.1.5: - resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==} - which-boxed-primitive@1.1.1: resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} engines: {node: '>= 0.4'} @@ -7442,10 +7367,6 @@ snapshots: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv-formats@3.0.1(ajv@8.18.0): - optionalDependencies: - ajv: 8.18.0 - ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 @@ -7457,13 +7378,6 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.18.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-uri: 3.1.0 - json-schema-traverse: 1.0.0 - require-from-string: 2.0.2 - ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -7665,11 +7579,6 @@ snapshots: at-least-node@1.0.0: {} - atomically@2.1.1: - dependencies: - stubborn-fs: 2.0.0 - when-exit: 2.1.5 - autoprefixer@10.4.22(postcss@8.5.6): dependencies: browserslist: 4.28.0 @@ -8009,18 +7918,6 @@ snapshots: tree-kill: 1.2.2 yargs: 17.7.2 - conf@14.0.0: - dependencies: - ajv: 8.18.0 - ajv-formats: 3.0.1(ajv@8.18.0) - atomically: 2.1.1 - debounce-fn: 6.0.0 - dot-prop: 9.0.0 - env-paths: 3.0.0 - json-schema-typed: 8.0.2 - semver: 7.7.3 - uint8array-extras: 1.5.0 - confbox@0.2.2: {} config-file-ts@0.2.8-rc1: @@ -8111,10 +8008,6 @@ snapshots: dependencies: '@babel/runtime': 7.28.4 - debounce-fn@6.0.0: - dependencies: - mimic-function: 5.0.1 - debug@3.2.7: dependencies: ms: 2.1.3 @@ -8212,10 +8105,6 @@ snapshots: dependencies: esutils: 2.0.3 - dot-prop@9.0.0: - dependencies: - type-fest: 4.41.0 - dotenv-expand@11.0.7: dependencies: dotenv: 16.6.1 @@ -8279,8 +8168,6 @@ snapshots: - electron-builder-squirrel-windows - supports-color - electron-is-dev@3.0.1: {} - electron-publish@25.1.7: dependencies: '@types/fs-extra': 9.0.13 @@ -8293,11 +8180,6 @@ snapshots: transitivePeerDependencies: - supports-color - electron-store@10.1.0: - dependencies: - conf: 14.0.0 - type-fest: 4.41.0 - electron-to-chromium@1.5.256: {} electron@34.5.8: @@ -8362,8 +8244,6 @@ snapshots: env-paths@2.2.1: {} - env-paths@3.0.0: {} - err-code@2.0.3: {} es-abstract@1.24.0: @@ -8878,8 +8758,6 @@ snapshots: fast-levenshtein@2.0.6: {} - fast-uri@3.1.0: {} - fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -9513,10 +9391,6 @@ snapshots: json-schema-traverse@0.4.1: {} - json-schema-traverse@1.0.0: {} - - json-schema-typed@8.0.2: {} - json-stable-stringify-without-jsonify@1.0.1: {} json-stringify-safe@5.0.1: @@ -9801,8 +9675,6 @@ snapshots: mimic-fn@2.1.0: {} - mimic-function@5.0.1: {} - mimic-response@1.0.1: {} mimic-response@3.1.0: {} @@ -10369,8 +10241,6 @@ snapshots: require-directory@2.1.1: {} - require-from-string@2.0.2: {} - resedit@1.7.2: dependencies: pe-library: 0.4.1 @@ -10835,12 +10705,6 @@ snapshots: strip-json-comments@3.1.1: {} - stubborn-fs@2.0.0: - dependencies: - stubborn-utils: 1.0.2 - - stubborn-utils@1.0.2: {} - styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): dependencies: client-only: 0.0.1 @@ -11123,8 +10987,6 @@ snapshots: typescript@5.9.3: {} - uint8array-extras@1.5.0: {} - unbox-primitive@1.1.0: dependencies: call-bound: 1.0.4 @@ -11273,8 +11135,6 @@ snapshots: dependencies: sdp: 3.2.1 - when-exit@2.1.5: {} - which-boxed-primitive@1.1.1: dependencies: is-bigint: 1.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a22cffb..b1c6baa 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -11,5 +11,6 @@ onlyBuiltDependencies: - '@prisma/client' - '@prisma/engines' - bcrypt + - electron - esbuild - prisma From f255c3248fd6bf24e5cc46cbd034a53376e269b8 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:08:09 +0200 Subject: [PATCH 03/19] fix(frontend): stabilize Next build --- apps/frontend/next.config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/frontend/next.config.js b/apps/frontend/next.config.js index e76db56..5588965 100644 --- a/apps/frontend/next.config.js +++ b/apps/frontend/next.config.js @@ -62,6 +62,7 @@ const nextConfig = { experimental: { optimizePackageImports: ['lucide-react', 'framer-motion'], + webpackBuildWorker: false, }, } From f05a856bb512a315331b279a16a5636fb3387ae2 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:25:11 +0200 Subject: [PATCH 04/19] fix: default NEXT_PUBLIC_API_URL for CI builds --- apps/frontend/next-env.d.ts | 2 +- apps/frontend/src/lib/api/client.ts | 6 +----- packages/lib/src/api.ts | 7 +------ packages/lib/src/getAvatarUrl.ts | 6 ++++-- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/apps/frontend/next-env.d.ts b/apps/frontend/next-env.d.ts index c4b7818..9edff1c 100644 --- a/apps/frontend/next-env.d.ts +++ b/apps/frontend/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/frontend/src/lib/api/client.ts b/apps/frontend/src/lib/api/client.ts index 8060dd1..56d28fe 100644 --- a/apps/frontend/src/lib/api/client.ts +++ b/apps/frontend/src/lib/api/client.ts @@ -1,10 +1,6 @@ import { getAvatarUrl } from "@openchat/lib" -const RAW_API_URL = process.env.NEXT_PUBLIC_API_URL - -if (!RAW_API_URL) { - throw new Error("NEXT_PUBLIC_API_URL is not defined") -} +const RAW_API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000" const API_URL = RAW_API_URL.replace(/\/+$/, "") diff --git a/packages/lib/src/api.ts b/packages/lib/src/api.ts index f01445c..bb636b8 100644 --- a/packages/lib/src/api.ts +++ b/packages/lib/src/api.ts @@ -1,8 +1,4 @@ -const RAW_API_URL = process.env.NEXT_PUBLIC_API_URL!; - -if (!RAW_API_URL) { - throw new Error("NEXT_PUBLIC_API_URL is not defined"); -} +const RAW_API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"; export const API_URL = RAW_API_URL.replace(/\/+$/, ""); @@ -14,4 +10,3 @@ export function api(path: string, options?: RequestInit) { ...options, }); } - diff --git a/packages/lib/src/getAvatarUrl.ts b/packages/lib/src/getAvatarUrl.ts index f904ccf..aa97db7 100644 --- a/packages/lib/src/getAvatarUrl.ts +++ b/packages/lib/src/getAvatarUrl.ts @@ -5,9 +5,11 @@ export function getAvatarUrl(avatar?: string | null) { return avatar } + const baseUrl = (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000').replace(/\/+$/, '') + if (avatar.startsWith('/')) { - return `${process.env.NEXT_PUBLIC_API_URL}${avatar}` + return `${baseUrl}${avatar}` } - return `${process.env.NEXT_PUBLIC_API_URL}/uploads/${avatar}` + return `${baseUrl}/uploads/${avatar}` } From 8b6b536275cb089859ef420143c5a44104e7d04d Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:33:04 +0200 Subject: [PATCH 05/19] fix(desktop): pin electron version for CI --- desktop/electron-builder.json | 1 + desktop/package.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index 705a4a5..f675a14 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -1,6 +1,7 @@ { "appId": "com.openchat.app", "productName": "OpenChat", + "electronVersion": "34.5.8", "asarUnpack": ["renderer/**"], "directories": { "output": "dist", diff --git a/desktop/package.json b/desktop/package.json index d378d1e..ec05d74 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -12,7 +12,7 @@ "dependencies": { }, "devDependencies": { - "electron": "^34.5.8", + "electron": "34.5.8", "electron-builder": "^25.1.8", "tsx": "^4.19.2", "typescript": "^5.7.3" diff --git a/package.json b/package.json index 3b65882..35d83aa 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "concurrently": "^8.2.2", - "electron": "^34.5.8", + "electron": "34.5.8", "eslint": "^9.39.1", "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.4.24", From e11549a41b10b34e8ce282ebd51b033ccf0ea1b9 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:41:36 +0200 Subject: [PATCH 06/19] chore: update lockfile for pinned electron --- pnpm-lock.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29502bf..ce7d7c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -106,7 +106,7 @@ importers: specifier: ^8.2.2 version: 8.2.2 electron: - specifier: ^34.5.8 + specifier: 34.5.8 version: 34.5.8 eslint: specifier: ^9.39.1 @@ -345,7 +345,7 @@ importers: desktop: devDependencies: electron: - specifier: ^34.5.8 + specifier: 34.5.8 version: 34.5.8 electron-builder: specifier: ^25.1.8 From b5c1cdd41335c8a1d4b8ed17907548d933908c3b Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:46:38 +0200 Subject: [PATCH 07/19] fix(ci): skip desktop prod-deps install in electron-builder --- desktop/beforeBuild.cjs | 13 +++++++++++++ desktop/electron-builder.json | 1 + 2 files changed, 14 insertions(+) create mode 100644 desktop/beforeBuild.cjs diff --git a/desktop/beforeBuild.cjs b/desktop/beforeBuild.cjs new file mode 100644 index 0000000..5abb9b6 --- /dev/null +++ b/desktop/beforeBuild.cjs @@ -0,0 +1,13 @@ +/** + * electron-builder calls this hook before installing/rebuilding production dependencies. + * + * In this monorepo, running a production-only install from inside `desktop/` can cause pnpm + * to operate at the workspace root and prune dev dependencies that electron-builder still needs + * (like `app-builder-bin`), leading to ENOENT in CI. + * + * Returning `false` skips dependency install/rebuild steps. + */ +exports.default = async function beforeBuild() { + return false; +}; + diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index f675a14..d30f7e6 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -2,6 +2,7 @@ "appId": "com.openchat.app", "productName": "OpenChat", "electronVersion": "34.5.8", + "beforeBuild": "./beforeBuild.cjs", "asarUnpack": ["renderer/**"], "directories": { "output": "dist", From a7a5b6f16feba984cda6369f0de42f7a41321973 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:51:15 +0200 Subject: [PATCH 08/19] chore(desktop): add metadata for linux packaging --- desktop/electron-builder.json | 3 ++- desktop/package.json | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index d30f7e6..dbe7565 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -21,7 +21,8 @@ "linux": { "target": ["AppImage", "deb"], "icon": "icon.png", - "category": "Chat" + "category": "Chat", + "maintainer": "OpenChat " }, "nsis": { "oneClick": false, diff --git a/desktop/package.json b/desktop/package.json index ec05d74..a3177da 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -1,6 +1,13 @@ { "name": "@openchat/desktop", "version": "1.0.0", + "description": "OpenChat desktop app", + "homepage": "https://openchat.io", + "repository": { + "type": "git", + "url": "https://github.com/DevMuhammed3/OpenChat.git" + }, + "author": "OpenChat ", "main": "build/main.js", "scripts": { "dev": "pnpm run build && pnpm exec electron .", From b102b77ac8ce2c8a148c32ca8eba0b15c991d2f1 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 18:56:36 +0200 Subject: [PATCH 09/19] fix(ci): avoid scoped deb artifact path --- desktop/electron-builder.json | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index dbe7565..22016dd 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -2,6 +2,7 @@ "appId": "com.openchat.app", "productName": "OpenChat", "electronVersion": "34.5.8", + "artifactName": "${productName}-${version}-${arch}.${ext}", "beforeBuild": "./beforeBuild.cjs", "asarUnpack": ["renderer/**"], "directories": { From 584e201d223ac5a81b8b4dcf569c68aa89ba834c Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 19:06:42 +0200 Subject: [PATCH 10/19] feat(desktop): start at auth and show native menu --- desktop/main.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/desktop/main.ts b/desktop/main.ts index ae5993e..436671c 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -8,6 +8,7 @@ const isDev = !app.isPackaged; let mainWindow: BrowserWindow | null = null; let nextServerProcess: ChildProcessWithoutNullStreams | null = null; let nextServerPort: number | null = null; +const START_PATH = "/auth"; type WindowState = { width: number; height: number; x?: number; y?: number }; const defaultWindowState: WindowState = { width: 1200, height: 800 }; @@ -152,6 +153,12 @@ async function resolveAppUrl(): Promise { return `http://127.0.0.1:${port}`; } +function buildStartUrl(baseUrl: string) { + const url = new URL(baseUrl); + url.pathname = START_PATH; + return url.toString(); +} + async function createWindow() { const windowState = readWindowState(); @@ -171,8 +178,13 @@ async function createWindow() { preload: resolveAppFile("preload.cjs"), }, icon: resolveAppFile("icon.png"), + autoHideMenuBar: false, }); + if (process.platform !== "darwin") { + mainWindow.setMenuBarVisibility(true); + } + mainWindow.on("resize", () => { if (!mainWindow) return; const { width, height, x, y } = mainWindow.getBounds(); @@ -186,7 +198,7 @@ async function createWindow() { }); try { - const url = await resolveAppUrl(); + const url = buildStartUrl(await resolveAppUrl()); await mainWindow.loadURL(url); } catch (err) { const message = err instanceof Error ? err.message : String(err); From fd0bd1da1e39107f7fdec225c63f738132f30cbd Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Thu, 2 Apr 2026 19:12:21 +0200 Subject: [PATCH 11/19] feat(desktop): default API URL to api.openchat.qzz.io --- desktop/main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/desktop/main.ts b/desktop/main.ts index 436671c..197cb2d 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -9,6 +9,7 @@ let mainWindow: BrowserWindow | null = null; let nextServerProcess: ChildProcessWithoutNullStreams | null = null; let nextServerPort: number | null = null; const START_PATH = "/auth"; +const DEFAULT_API_URL = "https://api.openchat.qzz.io"; type WindowState = { width: number; height: number; x?: number; y?: number }; const defaultWindowState: WindowState = { width: 1200, height: 800 }; @@ -130,6 +131,7 @@ async function ensureNextServerStarted(): Promise { NODE_ENV: "production", HOSTNAME: "127.0.0.1", PORT: String(nextServerPort), + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL, }, stdio: "pipe", }, From b74e4748804379a1cbd591e2f30480933803cb35 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 00:14:19 +0200 Subject: [PATCH 12/19] feat: runtime API config for desktop builds --- .../src/app/settings/profile/page.tsx | 4 ++-- .../src/app/zone/_components/MobileLayout.tsx | 4 ++-- .../src/app/zone/_components/UserBar.tsx | 5 +++-- apps/frontend/src/app/zone/profile/page.tsx | 14 +++++++----- apps/frontend/src/lib/api/client.ts | 6 ++--- desktop/main.ts | 7 +++++- desktop/preload.ts | 8 +++++++ packages/lib/src/api.ts | 7 +++--- packages/lib/src/config.ts | 22 +++++++++++++++++++ packages/lib/src/getAvatarUrl.ts | 6 ++++- packages/lib/src/index.ts | 1 + 11 files changed, 62 insertions(+), 22 deletions(-) create mode 100644 packages/lib/src/config.ts diff --git a/apps/frontend/src/app/settings/profile/page.tsx b/apps/frontend/src/app/settings/profile/page.tsx index 918e0e8..b4492ab 100644 --- a/apps/frontend/src/app/settings/profile/page.tsx +++ b/apps/frontend/src/app/settings/profile/page.tsx @@ -18,7 +18,7 @@ import { useUserStore } from '@/app/stores/user-store' import { toast } from 'sonner' import { useForm } from 'react-hook-form' import { useState, useRef, useEffect } from 'react' -import { api } from '@openchat/lib' +import { api, getAvatarUrl } from '@openchat/lib' type FormValues = { name: string @@ -217,7 +217,7 @@ export default function ProfilePage() { {user.avatar ? ( ) : ( diff --git a/apps/frontend/src/app/zone/_components/MobileLayout.tsx b/apps/frontend/src/app/zone/_components/MobileLayout.tsx index af43736..8351110 100644 --- a/apps/frontend/src/app/zone/_components/MobileLayout.tsx +++ b/apps/frontend/src/app/zone/_components/MobileLayout.tsx @@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef } from 'react' import { useRouter, usePathname } from 'next/navigation' -import { cn } from '@openchat/lib' +import { cn, getAvatarUrl } from '@openchat/lib' import Link from 'next/link' import { Radiation } from 'lucide-react' @@ -182,7 +182,7 @@ function TabIcon({ tabId, isActive, user }: { tabId: string; isActive: boolean; case 'profile': return user?.avatar ? ( Profile { try { - await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/logout`, { + const apiUrl = getApiBaseUrl() + await fetch(`${apiUrl}/auth/logout`, { method: 'POST', credentials: 'include', }) diff --git a/apps/frontend/src/app/zone/profile/page.tsx b/apps/frontend/src/app/zone/profile/page.tsx index 723851e..d58dd85 100644 --- a/apps/frontend/src/app/zone/profile/page.tsx +++ b/apps/frontend/src/app/zone/profile/page.tsx @@ -3,7 +3,7 @@ import { useEffect, useState } from 'react' import { useRouter } from 'next/navigation' import { Settings, Shield, Bell, Lock, LogOut, ChevronRight, User, LogOut as LogOutIcon, Heart, UserPlus, Users, MessageCircle } from 'lucide-react' -import { cn } from '@openchat/lib' +import { cn, getApiBaseUrl, getAvatarUrl } from '@openchat/lib' interface UserProfile { id: string @@ -21,9 +21,10 @@ export default function ProfilePage() { const [stats, setStats] = useState({ friends: 0, zones: 0, messages: 0 }) useEffect(() => { + const apiUrl = getApiBaseUrl() const fetchUser = async () => { try { - const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/me`, { + const res = await fetch(`${apiUrl}/auth/me`, { credentials: 'include', }) const data = await res.json() @@ -32,8 +33,8 @@ export default function ProfilePage() { // Fetch additional stats const [friendsRes, chatsRes] = await Promise.all([ - fetch(`${process.env.NEXT_PUBLIC_API_URL}/friends`, { credentials: 'include' }), - fetch(`${process.env.NEXT_PUBLIC_API_URL}/chats`, { credentials: 'include' }), + fetch(`${apiUrl}/friends`, { credentials: 'include' }), + fetch(`${apiUrl}/chats`, { credentials: 'include' }), ]) const friendsData = await friendsRes.json() @@ -57,7 +58,8 @@ export default function ProfilePage() { const handleLogout = async () => { try { - await fetch(`${process.env.NEXT_PUBLIC_API_URL}/auth/logout`, { + const apiUrl = getApiBaseUrl() + await fetch(`${apiUrl}/auth/logout`, { method: 'POST', credentials: 'include', }) @@ -92,7 +94,7 @@ export default function ProfilePage() {
{user?.avatar ? ( {user?.username} diff --git a/apps/frontend/src/lib/api/client.ts b/apps/frontend/src/lib/api/client.ts index 56d28fe..202ba63 100644 --- a/apps/frontend/src/lib/api/client.ts +++ b/apps/frontend/src/lib/api/client.ts @@ -1,8 +1,6 @@ -import { getAvatarUrl } from "@openchat/lib" +import { getApiBaseUrl, getAvatarUrl } from "@openchat/lib" -const RAW_API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000" - -const API_URL = RAW_API_URL.replace(/\/+$/, "") +const API_URL = getApiBaseUrl() type PrimitiveQueryValue = string | number | boolean | null | undefined type QueryValue = PrimitiveQueryValue | PrimitiveQueryValue[] diff --git a/desktop/main.ts b/desktop/main.ts index 197cb2d..b75a567 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -11,6 +11,10 @@ let nextServerPort: number | null = null; const START_PATH = "/auth"; const DEFAULT_API_URL = "https://api.openchat.qzz.io"; +function getApiUrl() { + return process.env.OPENCHAT_API_URL || process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL; +} + type WindowState = { width: number; height: number; x?: number; y?: number }; const defaultWindowState: WindowState = { width: 1200, height: 800 }; @@ -131,7 +135,7 @@ async function ensureNextServerStarted(): Promise { NODE_ENV: "production", HOSTNAME: "127.0.0.1", PORT: String(nextServerPort), - NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL, + NEXT_PUBLIC_API_URL: getApiUrl(), }, stdio: "pipe", }, @@ -178,6 +182,7 @@ async function createWindow() { nodeIntegration: false, contextIsolation: true, preload: resolveAppFile("preload.cjs"), + additionalArguments: [`--openchat-api-url=${encodeURIComponent(getApiUrl())}`], }, icon: resolveAppFile("icon.png"), autoHideMenuBar: false, diff --git a/desktop/preload.ts b/desktop/preload.ts index 4844a4c..af51caa 100644 --- a/desktop/preload.ts +++ b/desktop/preload.ts @@ -1,5 +1,9 @@ import { contextBridge, ipcRenderer } from "electron"; +const apiArgPrefix = "--openchat-api-url="; +const apiUrlArg = process.argv.find((arg) => arg.startsWith(apiArgPrefix)); +const apiUrl = apiUrlArg ? decodeURIComponent(apiUrlArg.slice(apiArgPrefix.length)) : undefined; + // Expose a minimal API to the renderer process contextBridge.exposeInMainWorld("electron", { platform: process.platform, @@ -18,3 +22,7 @@ contextBridge.exposeInMainWorld("electron", { } }, }); + +contextBridge.exposeInMainWorld("openchatConfig", { + apiUrl, +}); diff --git a/packages/lib/src/api.ts b/packages/lib/src/api.ts index bb636b8..7c80c32 100644 --- a/packages/lib/src/api.ts +++ b/packages/lib/src/api.ts @@ -1,11 +1,10 @@ -const RAW_API_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:4000"; - -export const API_URL = RAW_API_URL.replace(/\/+$/, ""); +import { getApiBaseUrl } from "./config"; export function api(path: string, options?: RequestInit) { + const apiUrl = getApiBaseUrl(); const cleanPath = path.startsWith("/") ? path : `/${path}`; - return fetch(`${API_URL}${cleanPath}`, { + return fetch(`${apiUrl}${cleanPath}`, { credentials: "include", ...options, }); diff --git a/packages/lib/src/config.ts b/packages/lib/src/config.ts new file mode 100644 index 0000000..e98355b --- /dev/null +++ b/packages/lib/src/config.ts @@ -0,0 +1,22 @@ +const DEFAULT_API_URL = "https://api.openchat.qzz.io"; + +function normalizeUrl(url: string) { + return url.trim().replace(/\/+$/, ""); +} + +export function getApiBaseUrl() { + const runtimeUrl = (globalThis as any)?.openchatConfig?.apiUrl; + if (typeof runtimeUrl === "string" && runtimeUrl.trim()) { + return normalizeUrl(runtimeUrl); + } + + const envUrl = + (typeof process !== "undefined" + ? process.env.OPENCHAT_API_URL || process.env.NEXT_PUBLIC_API_URL + : undefined) || DEFAULT_API_URL; + + return normalizeUrl(envUrl); +} + +export { DEFAULT_API_URL }; + diff --git a/packages/lib/src/getAvatarUrl.ts b/packages/lib/src/getAvatarUrl.ts index aa97db7..b0381e3 100644 --- a/packages/lib/src/getAvatarUrl.ts +++ b/packages/lib/src/getAvatarUrl.ts @@ -1,3 +1,5 @@ +import { getApiBaseUrl } from "./config" + export function getAvatarUrl(avatar?: string | null) { if (!avatar) return undefined @@ -5,7 +7,9 @@ export function getAvatarUrl(avatar?: string | null) { return avatar } - const baseUrl = (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000').replace(/\/+$/, '') + // Use the same resolution logic everywhere (Electron runtime config > env > default). + // This keeps desktop builds working even when env vars aren't set at build-time. + const baseUrl = getApiBaseUrl() if (avatar.startsWith('/')) { return `${baseUrl}${avatar}` diff --git a/packages/lib/src/index.ts b/packages/lib/src/index.ts index 66a018e..7585e6a 100644 --- a/packages/lib/src/index.ts +++ b/packages/lib/src/index.ts @@ -8,5 +8,6 @@ export * from "./audio/index" export { cn } from './utils' export { socket } from './socket' export { api } from "./api" +export { getApiBaseUrl } from "./config" export { playMessageSound } from "./sounds" export { getAvatarUrl } from "./getAvatarUrl" From 12acd64e199d120ee7190bbffe32265b685b5c6b Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 00:18:17 +0200 Subject: [PATCH 13/19] chore(windows): ship NSIS installer with shortcuts --- desktop/electron-builder.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index 22016dd..7c6caea 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -16,7 +16,7 @@ "icon": "icon.png" }, "win": { - "target": ["nsis", "portable"], + "target": ["nsis"], "icon": "icon.png" }, "linux": { @@ -29,6 +29,9 @@ "oneClick": false, "allowToChangeInstallationDirectory": true, "createDesktopShortcut": "always", + "createStartMenuShortcut": true, + "menuCategory": "OpenChat", + "runAfterFinish": true, "shortcutName": "OpenChat" } } From fdadd3c9bff8647a9ce0b99612e612398b3f6555 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 00:19:27 +0200 Subject: [PATCH 14/19] fix: validate runtime API URL --- desktop/main.ts | 19 ++++++++++++++++++- packages/lib/src/config.ts | 31 ++++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/desktop/main.ts b/desktop/main.ts index b75a567..41aa6e6 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -11,8 +11,25 @@ let nextServerPort: number | null = null; const START_PATH = "/auth"; const DEFAULT_API_URL = "https://api.openchat.qzz.io"; +function normalizeAndValidateApiUrl(value: string | undefined) { + if (!value) return null; + const trimmed = value.trim(); + if (!trimmed) return null; + + try { + const url = new URL(trimmed); + if (url.protocol !== "http:" && url.protocol !== "https:") return null; + return url.toString().replace(/\/+$/, ""); + } catch { + return null; + } +} + function getApiUrl() { - return process.env.OPENCHAT_API_URL || process.env.NEXT_PUBLIC_API_URL || DEFAULT_API_URL; + return ( + normalizeAndValidateApiUrl(process.env.OPENCHAT_API_URL || process.env.NEXT_PUBLIC_API_URL) ?? + DEFAULT_API_URL + ); } type WindowState = { width: number; height: number; x?: number; y?: number }; diff --git a/packages/lib/src/config.ts b/packages/lib/src/config.ts index e98355b..585dad9 100644 --- a/packages/lib/src/config.ts +++ b/packages/lib/src/config.ts @@ -4,19 +4,32 @@ function normalizeUrl(url: string) { return url.trim().replace(/\/+$/, ""); } -export function getApiBaseUrl() { - const runtimeUrl = (globalThis as any)?.openchatConfig?.apiUrl; - if (typeof runtimeUrl === "string" && runtimeUrl.trim()) { - return normalizeUrl(runtimeUrl); +function normalizeAndValidateApiUrl(value: unknown) { + if (typeof value !== "string") return null; + const trimmed = value.trim(); + if (!trimmed) return null; + + try { + const url = new URL(trimmed); + if (url.protocol !== "http:" && url.protocol !== "https:") return null; + return normalizeUrl(url.toString()); + } catch { + return null; } +} - const envUrl = - (typeof process !== "undefined" +export function getApiBaseUrl() { + const runtimeUrl = normalizeAndValidateApiUrl((globalThis as any)?.openchatConfig?.apiUrl); + if (runtimeUrl) return runtimeUrl; + + const envUrl = normalizeAndValidateApiUrl( + typeof process !== "undefined" ? process.env.OPENCHAT_API_URL || process.env.NEXT_PUBLIC_API_URL - : undefined) || DEFAULT_API_URL; + : undefined, + ); + if (envUrl) return envUrl; - return normalizeUrl(envUrl); + return DEFAULT_API_URL; } export { DEFAULT_API_URL }; - From 7d929630246795731fa2eedb2031500c6e5c22e4 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 00:28:02 +0200 Subject: [PATCH 15/19] feat(desktop): use https://openchat.qzz.io in production --- desktop/main.ts | 54 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/desktop/main.ts b/desktop/main.ts index 41aa6e6..92c6c76 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -9,6 +9,7 @@ let mainWindow: BrowserWindow | null = null; let nextServerProcess: ChildProcessWithoutNullStreams | null = null; let nextServerPort: number | null = null; const START_PATH = "/auth"; +const DEFAULT_WEB_URL = "https://openchat.qzz.io"; const DEFAULT_API_URL = "https://api.openchat.qzz.io"; function normalizeAndValidateApiUrl(value: string | undefined) { @@ -32,6 +33,10 @@ function getApiUrl() { ); } +function getWebUrl() { + return normalizeAndValidateApiUrl(process.env.OPENCHAT_WEB_URL) ?? DEFAULT_WEB_URL; +} + type WindowState = { width: number; height: number; x?: number; y?: number }; const defaultWindowState: WindowState = { width: 1200, height: 800 }; @@ -172,8 +177,7 @@ async function ensureNextServerStarted(): Promise { async function resolveAppUrl(): Promise { if (isDev) return "http://localhost:3000"; - const port = await ensureNextServerStarted(); - return `http://127.0.0.1:${port}`; + return getWebUrl(); } function buildStartUrl(baseUrl: string) { @@ -182,6 +186,31 @@ function buildStartUrl(baseUrl: string) { return url.toString(); } +function isAllowedNavigation(targetUrl: string) { + try { + const origin = new URL(targetUrl).origin; + if (origin === "http://localhost:3000") return true; + if (origin.startsWith("http://127.0.0.1:")) return true; + return origin === new URL(getWebUrl()).origin; + } catch { + return false; + } +} + +async function loadMainUrl() { + if (!mainWindow) return; + + const primaryUrl = buildStartUrl(await resolveAppUrl()); + try { + await mainWindow.loadURL(primaryUrl); + return; + } catch (err) { + if (isDev) throw err; + const port = await ensureNextServerStarted(); + await mainWindow.loadURL(buildStartUrl(`http://127.0.0.1:${port}`)); + } +} + async function createWindow() { const windowState = readWindowState(); @@ -222,8 +251,7 @@ async function createWindow() { }); try { - const url = buildStartUrl(await resolveAppUrl()); - await mainWindow.loadURL(url); + await loadMainUrl(); } catch (err) { const message = err instanceof Error ? err.message : String(err); dialog.showErrorBox("OpenChat failed to start", message); @@ -237,6 +265,13 @@ async function createWindow() { return { action: "deny" }; }); + mainWindow.webContents.on("will-navigate", (event, url) => { + if (!isAllowedNavigation(url)) { + event.preventDefault(); + void shell.openExternal(url); + } + }); + mainWindow.on("closed", () => { mainWindow = null; }); @@ -279,6 +314,17 @@ function setAppMenu() { Menu.setApplicationMenu(menu); } +const gotSingleInstanceLock = app.requestSingleInstanceLock(); +if (!gotSingleInstanceLock) { + app.quit(); +} else { + app.on("second-instance", () => { + if (!mainWindow) return; + if (mainWindow.isMinimized()) mainWindow.restore(); + mainWindow.focus(); + }); +} + app.whenReady().then(() => { setAppMenu(); void createWindow(); From 087305398cc48549ac79ef9105629276597a7292 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 00:47:44 +0200 Subject: [PATCH 16/19] fix(linux): skip deb build without libcrypt --- desktop/package.json | 2 +- desktop/scripts/dist.mjs | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 desktop/scripts/dist.mjs diff --git a/desktop/package.json b/desktop/package.json index a3177da..d48cb0f 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -14,7 +14,7 @@ "build": "tsc -p tsconfig.json", "prepare:renderer": "node ./scripts/prepare-renderer.mjs", "package": "pnpm run build && pnpm exec electron-builder", - "dist": "pnpm run build && pnpm exec electron-builder" + "dist": "pnpm run build && node ./scripts/dist.mjs" }, "dependencies": { }, diff --git a/desktop/scripts/dist.mjs b/desktop/scripts/dist.mjs new file mode 100644 index 0000000..080a1b8 --- /dev/null +++ b/desktop/scripts/dist.mjs @@ -0,0 +1,43 @@ +import { spawnSync } from "node:child_process"; +import fs from "node:fs"; + +function hasLibcryptSo1() { + const candidates = [ + "/lib64/libcrypt.so.1", + "/usr/lib64/libcrypt.so.1", + "/lib/x86_64-linux-gnu/libcrypt.so.1", + "/usr/lib/x86_64-linux-gnu/libcrypt.so.1", + ]; + + return candidates.some((p) => fs.existsSync(p)); +} + +function run(cmd, args) { + const result = spawnSync(cmd, args, { stdio: "inherit" }); + if (result.status !== 0) process.exit(result.status ?? 1); +} + +function printLinuxDebHint() { + // Fedora/RHEL family usually needs libxcrypt-compat to provide libcrypt.so.1 + console.warn( + [ + "", + "NOTE: Skipping .deb packaging because `libcrypt.so.1` is missing.", + "On Fedora you can enable .deb builds by installing: `sudo dnf install -y libxcrypt-compat`", + "Or set `OPENCHAT_SKIP_DEB=1` to silence this message and always build AppImage only.", + "", + ].join("\n"), + ); +} + +const skipDeb = process.env.OPENCHAT_SKIP_DEB === "1" || process.env.OPENCHAT_SKIP_DEB === "true"; + +// electron-builder will build what’s configured in `desktop/electron-builder.json` by default. +// On Fedora, the bundled `fpm` (used for .deb) requires `libcrypt.so.1`, which is not installed by default. +if (process.platform === "linux" && (skipDeb || !hasLibcryptSo1())) { + if (!skipDeb && !hasLibcryptSo1()) printLinuxDebHint(); + // Build only AppImage to keep local Linux builds reliable. + run("pnpm", ["exec", "electron-builder", "--linux", "AppImage"]); +} else { + run("pnpm", ["exec", "electron-builder"]); +} From e7c463f0fac25c565f889ac6c87819a4861de13b Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 02:10:56 +0200 Subject: [PATCH 17/19] refactor(desktop): remote-only load with offline page --- desktop/electron-builder.json | 3 +- desktop/main.ts | 213 ++++++++++----------------- desktop/package.json | 1 - desktop/preload.ts | 21 +++ desktop/scripts/prepare-renderer.mjs | 66 --------- desktop/ui/offline.html | 142 ++++++++++++++++++ package.json | 2 +- 7 files changed, 245 insertions(+), 203 deletions(-) delete mode 100644 desktop/scripts/prepare-renderer.mjs create mode 100644 desktop/ui/offline.html diff --git a/desktop/electron-builder.json b/desktop/electron-builder.json index 7c6caea..238d561 100644 --- a/desktop/electron-builder.json +++ b/desktop/electron-builder.json @@ -4,12 +4,11 @@ "electronVersion": "34.5.8", "artifactName": "${productName}-${version}-${arch}.${ext}", "beforeBuild": "./beforeBuild.cjs", - "asarUnpack": ["renderer/**"], "directories": { "output": "dist", "buildResources": "assets" }, - "files": ["build/**/*", "renderer/**", "package.json", "preload.cjs", "icon.png"], + "files": ["build/**/*", "ui/**", "package.json", "preload.cjs", "icon.png"], "mac": { "category": "public.app-category.social-networking", "target": ["dmg", "zip"], diff --git a/desktop/main.ts b/desktop/main.ts index 92c6c76..f192c48 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -1,16 +1,13 @@ -import { app, BrowserWindow, shell, Menu, dialog } from "electron"; +import { app, BrowserWindow, shell, Menu, ipcMain } from "electron"; import path from "path"; import fs from "fs"; -import net from "net"; -import { spawn, type ChildProcessWithoutNullStreams } from "child_process"; const isDev = !app.isPackaged; let mainWindow: BrowserWindow | null = null; -let nextServerProcess: ChildProcessWithoutNullStreams | null = null; -let nextServerPort: number | null = null; const START_PATH = "/auth"; -const DEFAULT_WEB_URL = "https://openchat.qzz.io"; +const WEB_ORIGIN = "https://openchat.qzz.io"; const DEFAULT_API_URL = "https://api.openchat.qzz.io"; +let isShowingOfflinePage = false; function normalizeAndValidateApiUrl(value: string | undefined) { if (!value) return null; @@ -33,10 +30,6 @@ function getApiUrl() { ); } -function getWebUrl() { - return normalizeAndValidateApiUrl(process.env.OPENCHAT_WEB_URL) ?? DEFAULT_WEB_URL; -} - type WindowState = { width: number; height: number; x?: number; y?: number }; const defaultWindowState: WindowState = { width: 1200, height: 800 }; @@ -82,104 +75,6 @@ function resolveAppFile(relPath: string): string { return candidates[0]; } -function isPortFree(port: number): Promise { - return new Promise((resolve) => { - const tester = net.createServer(); - tester.unref(); - tester.on("error", () => resolve(false)); - tester.listen(port, "127.0.0.1", () => { - tester.close(() => resolve(true)); - }); - }); -} - -async function pickPort(startPort = 3000, maxTries = 50): Promise { - for (let i = 0; i < maxTries; i++) { - const port = startPort + i; - // eslint-disable-next-line no-await-in-loop - if (await isPortFree(port)) return port; - } - throw new Error(`Could not find a free port starting at ${startPort}.`); -} - -function waitForTcp(host: string, port: number, timeoutMs = 20_000): Promise { - const start = Date.now(); - - return new Promise((resolve, reject) => { - const tryOnce = () => { - const socket = net.connect({ host, port }); - socket.once("connect", () => { - socket.end(); - resolve(); - }); - socket.once("error", () => { - socket.destroy(); - if (Date.now() - start > timeoutMs) { - reject(new Error(`Timed out waiting for ${host}:${port}`)); - return; - } - setTimeout(tryOnce, 200); - }); - }; - - tryOnce(); - }); -} - -async function ensureNextServerStarted(): Promise { - if (nextServerPort !== null) return nextServerPort; - - const rendererDirCandidates = [ - path.join(app.getAppPath(), "renderer"), - path.join(process.resourcesPath, "app.asar.unpacked", "renderer"), - ]; - const rendererDir = rendererDirCandidates.find((p) => fs.existsSync(p)) ?? rendererDirCandidates[0]; - const serverEntryCandidates = [ - path.join(rendererDir, "server.js"), - path.join(rendererDir, "apps", "frontend", "server.js"), - ]; - const serverEntry = serverEntryCandidates.find((p) => fs.existsSync(p)); - if (!serverEntry) { - throw new Error( - `Missing Next server entry under ${rendererDir}. Build the desktop app first (pnpm build:desktop).`, - ); - } - - nextServerPort = await pickPort(3000); - - nextServerProcess = spawn( - process.execPath, - ["--runAsNode", serverEntry], - { - cwd: rendererDir, - env: { - ...process.env, - NODE_ENV: "production", - HOSTNAME: "127.0.0.1", - PORT: String(nextServerPort), - NEXT_PUBLIC_API_URL: getApiUrl(), - }, - stdio: "pipe", - }, - ); - - nextServerProcess.on("exit", (code, signal) => { - if (isDev) return; - console.error(`Next server exited (code=${code}, signal=${signal})`); - }); - - nextServerProcess.stdout.on("data", (buf) => console.log(String(buf).trimEnd())); - nextServerProcess.stderr.on("data", (buf) => console.error(String(buf).trimEnd())); - - await waitForTcp("127.0.0.1", nextServerPort); - return nextServerPort; -} - -async function resolveAppUrl(): Promise { - if (isDev) return "http://localhost:3000"; - return getWebUrl(); -} - function buildStartUrl(baseUrl: string) { const url = new URL(baseUrl); url.pathname = START_PATH; @@ -188,29 +83,53 @@ function buildStartUrl(baseUrl: string) { function isAllowedNavigation(targetUrl: string) { try { - const origin = new URL(targetUrl).origin; - if (origin === "http://localhost:3000") return true; - if (origin.startsWith("http://127.0.0.1:")) return true; - return origin === new URL(getWebUrl()).origin; + const parsed = new URL(targetUrl); + if (parsed.protocol === "file:") return true; // offline page + if (parsed.protocol === "devtools:") return true; + if (parsed.protocol === "about:") return true; + if (isDev && parsed.origin === "http://localhost:3000") return true; + return parsed.origin === WEB_ORIGIN; } catch { return false; } } -async function loadMainUrl() { - if (!mainWindow) return; +function getRemoteAuthUrl() { + return buildStartUrl(WEB_ORIGIN); +} - const primaryUrl = buildStartUrl(await resolveAppUrl()); +function isSafeHttpUrl(value: unknown): value is string { + if (typeof value !== "string") return false; + const trimmed = value.trim(); + if (!trimmed) return false; try { - await mainWindow.loadURL(primaryUrl); - return; - } catch (err) { - if (isDev) throw err; - const port = await ensureNextServerStarted(); - await mainWindow.loadURL(buildStartUrl(`http://127.0.0.1:${port}`)); + const url = new URL(trimmed); + return url.protocol === "http:" || url.protocol === "https:"; + } catch { + return false; } } +async function showOfflinePage(error?: { code?: number; description?: string; url?: string }) { + if (!mainWindow) return; + isShowingOfflinePage = true; + + const offlinePath = resolveAppFile(path.join("ui", "offline.html")); + const query: Record = { + url: error?.url ?? getRemoteAuthUrl(), + code: typeof error?.code === "number" ? String(error.code) : "", + desc: error?.description ?? "", + }; + + await mainWindow.loadFile(offlinePath, { query }); +} + +async function loadRemote() { + if (!mainWindow) return; + isShowingOfflinePage = false; + await mainWindow.loadURL(getRemoteAuthUrl()); +} + async function createWindow() { const windowState = readWindowState(); @@ -251,17 +170,30 @@ async function createWindow() { }); try { - await loadMainUrl(); + await loadRemote(); } catch (err) { - const message = err instanceof Error ? err.message : String(err); - dialog.showErrorBox("OpenChat failed to start", message); - app.quit(); - return; + if (isDev) throw err; + await showOfflinePage({ description: err instanceof Error ? err.message : String(err), url: getRemoteAuthUrl() }); } + mainWindow.webContents.on( + "did-fail-load", + (_event, errorCode, errorDescription, validatedURL, isMainFrame) => { + if (!isMainFrame) return; + if (!validatedURL || !validatedURL.startsWith(WEB_ORIGIN)) return; + if (isShowingOfflinePage) return; + void showOfflinePage({ code: errorCode, description: errorDescription, url: validatedURL }); + }, + ); + // Open external links in default browser mainWindow.webContents.setWindowOpenHandler(({ url }: { url: string }) => { - shell.openExternal(url); + if (isAllowedNavigation(url) && mainWindow) { + void mainWindow.loadURL(url); + return { action: "deny" }; + } + + void shell.openExternal(url); return { action: "deny" }; }); @@ -272,6 +204,21 @@ async function createWindow() { } }); + mainWindow.webContents.on("will-redirect", (event, url) => { + if (!isAllowedNavigation(url)) { + event.preventDefault(); + void shell.openExternal(url); + } + }); + + mainWindow.webContents.on("render-process-gone", (_event, details) => { + if (isDev) return; + void showOfflinePage({ + description: `Renderer crashed (${details.reason}). Please restart the app.`, + url: getRemoteAuthUrl(), + }); + }); + mainWindow.on("closed", () => { mainWindow = null; }); @@ -330,6 +277,12 @@ app.whenReady().then(() => { void createWindow(); }); +ipcMain.on("openchat:open-external", (event, url) => { + if (!event.senderFrame?.url?.startsWith("file:")) return; + if (!isSafeHttpUrl(url)) return; + void shell.openExternal(url); +}); + app.on("window-all-closed", () => { if (process.platform !== "darwin") { app.quit(); @@ -341,9 +294,3 @@ app.on("activate", () => { void createWindow(); } }); - -app.on("before-quit", () => { - if (nextServerProcess && !nextServerProcess.killed) { - nextServerProcess.kill(); - } -}); diff --git a/desktop/package.json b/desktop/package.json index d48cb0f..5925994 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -12,7 +12,6 @@ "scripts": { "dev": "pnpm run build && pnpm exec electron .", "build": "tsc -p tsconfig.json", - "prepare:renderer": "node ./scripts/prepare-renderer.mjs", "package": "pnpm run build && pnpm exec electron-builder", "dist": "pnpm run build && node ./scripts/dist.mjs" }, diff --git a/desktop/preload.ts b/desktop/preload.ts index af51caa..4d771cf 100644 --- a/desktop/preload.ts +++ b/desktop/preload.ts @@ -4,6 +4,18 @@ const apiArgPrefix = "--openchat-api-url="; const apiUrlArg = process.argv.find((arg) => arg.startsWith(apiArgPrefix)); const apiUrl = apiUrlArg ? decodeURIComponent(apiUrlArg.slice(apiArgPrefix.length)) : undefined; +function isSafeHttpUrl(value: unknown): value is string { + if (typeof value !== "string") return false; + const trimmed = value.trim(); + if (!trimmed) return false; + try { + const url = new URL(trimmed); + return url.protocol === "http:" || url.protocol === "https:"; + } catch { + return false; + } +} + // Expose a minimal API to the renderer process contextBridge.exposeInMainWorld("electron", { platform: process.platform, @@ -26,3 +38,12 @@ contextBridge.exposeInMainWorld("electron", { contextBridge.exposeInMainWorld("openchatConfig", { apiUrl, }); + +// Only for local error/offline pages shipped with the app (file:// origin). +contextBridge.exposeInMainWorld("openchatDesktop", { + openExternal: (url: unknown) => { + if (globalThis.location?.protocol !== "file:") return; + if (!isSafeHttpUrl(url)) return; + ipcRenderer.send("openchat:open-external", url); + }, +}); diff --git a/desktop/scripts/prepare-renderer.mjs b/desktop/scripts/prepare-renderer.mjs deleted file mode 100644 index 59b53ed..0000000 --- a/desktop/scripts/prepare-renderer.mjs +++ /dev/null @@ -1,66 +0,0 @@ -import path from "node:path"; -import fs from "node:fs/promises"; - -const repoRoot = path.resolve(import.meta.dirname, "..", ".."); -const desktopDir = path.resolve(import.meta.dirname, ".."); - -const frontendDir = path.join(repoRoot, "apps", "frontend"); -const nextDir = path.join(frontendDir, ".next"); - -const standaloneDir = path.join(nextDir, "standalone"); -const staticDir = path.join(nextDir, "static"); -const publicDir = path.join(frontendDir, "public"); - -const rendererDir = path.join(desktopDir, "renderer"); - -async function exists(p) { - try { - await fs.access(p); - return true; - } catch { - return false; - } -} - -async function main() { - if (!(await exists(standaloneDir))) { - throw new Error( - `Missing ${standaloneDir}. Run the frontend build first (pnpm --filter frontend build).`, - ); - } - - await fs.rm(rendererDir, { recursive: true, force: true }); - await fs.mkdir(rendererDir, { recursive: true }); - - // Copy the standalone server bundle (includes server.js + minimal node_modules) - await fs.cp(standaloneDir, rendererDir, { recursive: true }); - - const serverEntryCandidates = [ - path.join(rendererDir, "server.js"), - path.join(rendererDir, "apps", "frontend", "server.js"), - ]; - const serverEntry = await (async () => { - for (const candidate of serverEntryCandidates) { - // eslint-disable-next-line no-await-in-loop - if (await exists(candidate)) return candidate; - } - return null; - })(); - - if (!serverEntry) { - throw new Error(`Could not find server.js inside ${rendererDir}`); - } - - const serverBaseDir = path.dirname(serverEntry); - - if (await exists(staticDir)) { - await fs.mkdir(path.join(serverBaseDir, ".next"), { recursive: true }); - await fs.cp(staticDir, path.join(serverBaseDir, ".next", "static"), { recursive: true }); - } - - if (await exists(publicDir)) { - await fs.cp(publicDir, path.join(serverBaseDir, "public"), { recursive: true }); - } -} - -await main(); diff --git a/desktop/ui/offline.html b/desktop/ui/offline.html new file mode 100644 index 0000000..4a1838a --- /dev/null +++ b/desktop/ui/offline.html @@ -0,0 +1,142 @@ + + + + + + + OpenChat is offline + + + +
+

OpenChat couldn’t load

+

+ The website may be down or your connection may be offline. This app is remote-first and requires access to the + OpenChat web app. +

+
+ + +
+ + +
+ + + + diff --git a/package.json b/package.json index 35d83aa..3fe512c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dev:frontend": "pnpm --filter frontend dev", "dev:backend": "pnpm --filter backend dev", "dev:desktop": "concurrently \"pnpm dev:frontend\" \"wait-on http://localhost:3000 && pnpm --filter desktop dev\"", - "build:desktop": "pnpm --filter frontend build && pnpm --filter desktop prepare:renderer && pnpm --filter desktop build", + "build:desktop": "pnpm --filter desktop build", "dist:desktop": "pnpm build:desktop && pnpm --filter desktop dist", "dev": "concurrently \"pnpm dev:frontend\" \"pnpm dev:backend\"", "dev:all": "pnpm -w -r --parallel dev", From 0e6f0393693e94ff4db2c65dbe3a433d59b8b694 Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 02:15:33 +0200 Subject: [PATCH 18/19] fix(ci): run electron-builder via node cli --- apps/backend/uploads/1775169213769.jpg | Bin 0 -> 52366 bytes .../7ba56169-f503-4e0d-baab-e71085d91d86.jpg | Bin 0 -> 52366 bytes desktop/main.ts | 4 +++- desktop/scripts/dist.mjs | 11 +++++++++-- 4 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 apps/backend/uploads/1775169213769.jpg create mode 100644 apps/backend/uploads/zones/7ba56169-f503-4e0d-baab-e71085d91d86.jpg diff --git a/apps/backend/uploads/1775169213769.jpg b/apps/backend/uploads/1775169213769.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4bf47d178e3605a2b85b87ec997ba2ec2c977818 GIT binary patch literal 52366 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 literal 0 HcmV?d00001 diff --git a/apps/backend/uploads/zones/7ba56169-f503-4e0d-baab-e71085d91d86.jpg b/apps/backend/uploads/zones/7ba56169-f503-4e0d-baab-e71085d91d86.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4bf47d178e3605a2b85b87ec997ba2ec2c977818 GIT binary patch literal 52366 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 literal 0 HcmV?d00001 diff --git a/desktop/main.ts b/desktop/main.ts index f192c48..05629b4 100644 --- a/desktop/main.ts +++ b/desktop/main.ts @@ -150,13 +150,15 @@ async function createWindow() { additionalArguments: [`--openchat-api-url=${encodeURIComponent(getApiUrl())}`], }, icon: resolveAppFile("icon.png"), - autoHideMenuBar: false, + autoHideMenuBar: true, }); if (process.platform !== "darwin") { mainWindow.setMenuBarVisibility(true); } + mainWindow.setMenu(null); + mainWindow.on("resize", () => { if (!mainWindow) return; const { width, height, x, y } = mainWindow.getBounds(); diff --git a/desktop/scripts/dist.mjs b/desktop/scripts/dist.mjs index 080a1b8..4bab8e4 100644 --- a/desktop/scripts/dist.mjs +++ b/desktop/scripts/dist.mjs @@ -1,5 +1,6 @@ import { spawnSync } from "node:child_process"; import fs from "node:fs"; +import { createRequire } from "node:module"; function hasLibcryptSo1() { const candidates = [ @@ -12,6 +13,11 @@ function hasLibcryptSo1() { return candidates.some((p) => fs.existsSync(p)); } +function resolveElectronBuilderCli() { + const require = createRequire(import.meta.url); + return require.resolve("electron-builder/out/cli/cli.js"); +} + function run(cmd, args) { const result = spawnSync(cmd, args, { stdio: "inherit" }); if (result.status !== 0) process.exit(result.status ?? 1); @@ -31,13 +37,14 @@ function printLinuxDebHint() { } const skipDeb = process.env.OPENCHAT_SKIP_DEB === "1" || process.env.OPENCHAT_SKIP_DEB === "true"; +const electronBuilderCli = resolveElectronBuilderCli(); // electron-builder will build what’s configured in `desktop/electron-builder.json` by default. // On Fedora, the bundled `fpm` (used for .deb) requires `libcrypt.so.1`, which is not installed by default. if (process.platform === "linux" && (skipDeb || !hasLibcryptSo1())) { if (!skipDeb && !hasLibcryptSo1()) printLinuxDebHint(); // Build only AppImage to keep local Linux builds reliable. - run("pnpm", ["exec", "electron-builder", "--linux", "AppImage"]); + run(process.execPath, [electronBuilderCli, "--linux", "AppImage"]); } else { - run("pnpm", ["exec", "electron-builder"]); + run(process.execPath, [electronBuilderCli]); } From 0184c566d4aca0c41ed1f6ff5023eb5201d65a2a Mon Sep 17 00:00:00 2001 From: "Dev.Muhammed3" Date: Fri, 3 Apr 2026 14:37:46 +0200 Subject: [PATCH 19/19] feat: add logic for custom keyboard shortcuts --- apps/frontend/next-env.d.ts | 2 +- .../src/app/providers/ClientProviders.tsx | 2 + .../settings/_components/SettingsSidebar.tsx | 3 +- .../src/app/settings/keyboard/page.tsx | 213 ++++++++++++++++ .../components/KeyboardShortcutsListener.tsx | 8 + .../src/components/ShortcutSettings.tsx | 240 ++++++++++++++++++ .../src/hooks/useKeyboardShortcuts.ts | 71 ++++++ apps/frontend/src/lib/keyboard-shortcuts.ts | 90 +++++++ 8 files changed, 627 insertions(+), 2 deletions(-) create mode 100644 apps/frontend/src/app/settings/keyboard/page.tsx create mode 100644 apps/frontend/src/components/KeyboardShortcutsListener.tsx create mode 100644 apps/frontend/src/components/ShortcutSettings.tsx create mode 100644 apps/frontend/src/hooks/useKeyboardShortcuts.ts create mode 100644 apps/frontend/src/lib/keyboard-shortcuts.ts diff --git a/apps/frontend/next-env.d.ts b/apps/frontend/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/apps/frontend/next-env.d.ts +++ b/apps/frontend/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/frontend/src/app/providers/ClientProviders.tsx b/apps/frontend/src/app/providers/ClientProviders.tsx index 08f3768..08272d9 100644 --- a/apps/frontend/src/app/providers/ClientProviders.tsx +++ b/apps/frontend/src/app/providers/ClientProviders.tsx @@ -11,6 +11,7 @@ import { GoogleOAuthProvider } from "@react-oauth/google" import ChannelCallManager from '../zone/_components/ChannelCallManager' import { AppQueryProvider } from '@/lib/query/provider' import type { AppUser } from '@/features/user/types' +import { KeyboardShortcutsListener } from '@/components/KeyboardShortcutsListener' export default function ClientProviders({ children, @@ -27,6 +28,7 @@ export default function ClientProviders({ + {/**/} {children} diff --git a/apps/frontend/src/app/settings/_components/SettingsSidebar.tsx b/apps/frontend/src/app/settings/_components/SettingsSidebar.tsx index cc96000..65a9646 100644 --- a/apps/frontend/src/app/settings/_components/SettingsSidebar.tsx +++ b/apps/frontend/src/app/settings/_components/SettingsSidebar.tsx @@ -3,7 +3,7 @@ import Link from 'next/link' import { usePathname, useRouter } from 'next/navigation' import { api, cn } from '@openchat/lib' -import { User, Shield, Lock, Bell, Trash2, LogOut, ArrowLeft } from 'lucide-react' +import { User, Shield, Lock, Bell, Trash2, LogOut, ArrowLeft, Keyboard } from 'lucide-react' import { useUserStore } from '@/app/stores/user-store' import { useChatsStore } from '@/app/stores/chat-store' import { useFriendsStore } from '@/app/stores/friends-store' @@ -14,6 +14,7 @@ const tabs = [ { name: 'Security', href: '/settings/security', icon: Shield }, { name: 'Privacy', href: '/settings/privacy', icon: Lock }, { name: 'Notifications', href: '/settings/notifications', icon: Bell }, + { name: 'Keyboard', href: '/settings/keyboard', icon: Keyboard }, { name: 'Account', href: '/settings/account', icon: Trash2 }, ] diff --git a/apps/frontend/src/app/settings/keyboard/page.tsx b/apps/frontend/src/app/settings/keyboard/page.tsx new file mode 100644 index 0000000..6ad5bbf --- /dev/null +++ b/apps/frontend/src/app/settings/keyboard/page.tsx @@ -0,0 +1,213 @@ +'use client' + +import { useState, useEffect, useCallback, useRef } from 'react' +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from 'packages/ui' +import { Keyboard, X } from 'lucide-react' +import { toast } from 'sonner' +import { + loadShortcuts, + saveShortcuts, + formatShortcut, + isValidShortcut, + hasConflict, + normalizeKey, + MODIFIER_KEYS, + type StoredShortcuts, +} from '@/lib/keyboard-shortcuts' + +interface ShortcutRecorderProps { + action: 'mute' | 'deafen' + value: string + onChange: (value: string) => void + otherShortcut: string +} + +function ShortcutRecorder({ action, value, onChange, otherShortcut }: ShortcutRecorderProps) { + const [recording, setRecording] = useState(false) + const [currentKeys, setCurrentKeys] = useState([]) + const timeoutRef = useRef | null>(null) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + e.preventDefault() + e.stopPropagation() + + if (!recording) return + + const key = normalizeKey(e.key) + + if (key === 'escape') { + setRecording(false) + setCurrentKeys([]) + return + } + + const keys: string[] = [] + if (e.ctrlKey || e.metaKey) keys.push('ctrl') + if (e.shiftKey) keys.push('shift') + if (e.altKey) keys.push('alt') + if (e.metaKey) keys.push('meta') + + if (!MODIFIER_KEYS.has(key)) { + keys.push(key) + } + + if (keys.length > 0) { + setCurrentKeys(keys) + } + }, [recording]) + + const handleKeyUp = useCallback(() => { + if (!recording || currentKeys.length === 0) return + + if (!isValidShortcut(currentKeys)) { + toast.error('Shortcut must include at least one non-modifier key') + setCurrentKeys([]) + return + } + + if (hasConflict(currentKeys, { mute: action === 'mute' ? formatShortcut(currentKeys) : value, deafen: action === 'deafen' ? formatShortcut(currentKeys) : otherShortcut })) { + toast.error('This shortcut is already used') + setCurrentKeys([]) + return + } + + onChange(formatShortcut(currentKeys)) + setRecording(false) + setCurrentKeys([]) + }, [recording, currentKeys, onChange, value, otherShortcut, action]) + + useEffect(() => { + if (recording) { + document.addEventListener('keydown', handleKeyDown, true) + document.addEventListener('keyup', handleKeyUp, true) + } + + return () => { + document.removeEventListener('keydown', handleKeyDown, true) + document.removeEventListener('keyup', handleKeyUp, true) + } + }, [recording, handleKeyDown, handleKeyUp]) + + const startRecording = () => { + setRecording(true) + setCurrentKeys([]) + } + + const clearShortcut = (e: React.MouseEvent) => { + e.stopPropagation() + onChange('') + } + + const label = action === 'mute' ? 'Mute' : 'Deafen' + + return ( +
+ {label} +
+ {recording ? ( +
+
+ + {currentKeys.length > 0 ? formatShortcut(currentKeys) : 'Press keys...'} + +
+ ) : value ? ( +
+ + {value} + +
+ ) : ( + + )} +
+
+ ) +} + +export default function KeyboardShortcutsPage() { + const [shortcuts, setShortcuts] = useState({ mute: '', deafen: '' }) + const [isLoaded, setIsLoaded] = useState(false) + + useEffect(() => { + const loaded = loadShortcuts() + setShortcuts(loaded) + setIsLoaded(true) + }, []) + + const handleChange = useCallback((action: 'mute' | 'deafen', value: string) => { + setShortcuts(prev => { + const updated = { ...prev, [action]: value } + saveShortcuts(updated) + return updated + }) + if (value) { + toast.success(`${action === 'mute' ? 'Mute' : 'Deafen'} shortcut updated`) + } + }, []) + + if (!isLoaded) { + return null + } + + return ( +
+
+

Keyboard Shortcuts

+

+ Customize keyboard shortcuts for quick mute and deafen actions. +

+
+ + + + + + Shortcuts + + + Press the button to record a new shortcut. Use combinations like Ctrl+Shift+M or Alt+D. + + + + + handleChange('mute', value)} + otherShortcut={shortcuts.deafen} + /> + handleChange('deafen', value)} + otherShortcut={shortcuts.mute} + /> + + + + + + Tips + + +

• Shortcuts work only when the app window is focused

+

• Shortcuts are disabled when typing in input fields

+

• Press Escape to cancel recording

+

• Each shortcut must include at least one non-modifier key

+
+
+
+ ) +} \ No newline at end of file diff --git a/apps/frontend/src/components/KeyboardShortcutsListener.tsx b/apps/frontend/src/components/KeyboardShortcutsListener.tsx new file mode 100644 index 0000000..95d5446 --- /dev/null +++ b/apps/frontend/src/components/KeyboardShortcutsListener.tsx @@ -0,0 +1,8 @@ +'use client' + +import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts' + +export function KeyboardShortcutsListener() { + useKeyboardShortcuts() + return null +} \ No newline at end of file diff --git a/apps/frontend/src/components/ShortcutSettings.tsx b/apps/frontend/src/components/ShortcutSettings.tsx new file mode 100644 index 0000000..1217e58 --- /dev/null +++ b/apps/frontend/src/components/ShortcutSettings.tsx @@ -0,0 +1,240 @@ +'use client' + +import { useState, useEffect, useCallback, useRef } from 'react' +import { toast } from 'sonner' +import { Keyboard, X, RotateCcw } from 'lucide-react' +import { + loadShortcuts, + saveShortcuts, + formatShortcut, + isValidShortcut, + hasConflict, + normalizeKey, + MODIFIER_KEYS, + type StoredShortcuts, +} from '@/lib/keyboard-shortcuts' + +interface ShortcutRecorderProps { + action: 'mute' | 'deafen' + value: string + onChange: (value: string) => void + otherShortcut: string +} + +function ShortcutRecorder({ action, value, onChange, otherShortcut }: ShortcutRecorderProps) { + const [recording, setRecording] = useState(false) + const [currentKeys, setCurrentKeys] = useState([]) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + e.preventDefault() + e.stopPropagation() + + if (!recording) return + + const key = normalizeKey(e.key) + + if (key === 'escape') { + setRecording(false) + setCurrentKeys([]) + return + } + + const keys: string[] = [] + if (e.ctrlKey || e.metaKey) keys.push('ctrl') + if (e.shiftKey) keys.push('shift') + if (e.altKey) keys.push('alt') + if (e.metaKey) keys.push('meta') + + if (!MODIFIER_KEYS.has(key)) { + keys.push(key) + } + + if (keys.length > 0) { + setCurrentKeys(keys) + } + }, [recording]) + + const handleKeyUp = useCallback(() => { + if (!recording || currentKeys.length === 0) return + + if (!isValidShortcut(currentKeys)) { + toast.error('Shortcut must include at least one non-modifier key') + setCurrentKeys([]) + return + } + + const conflictCheck: StoredShortcuts = action === 'mute' + ? { mute: formatShortcut(currentKeys), deafen: otherShortcut } + : { mute: otherShortcut, deafen: formatShortcut(currentKeys) } + + if (hasConflict(currentKeys, conflictCheck)) { + toast.error('This shortcut is already used') + setCurrentKeys([]) + return + } + + onChange(formatShortcut(currentKeys)) + setRecording(false) + setCurrentKeys([]) + }, [recording, currentKeys, onChange, otherShortcut, action]) + + useEffect(() => { + if (recording) { + document.addEventListener('keydown', handleKeyDown, true) + document.addEventListener('keyup', handleKeyUp, true) + } + + return () => { + document.removeEventListener('keydown', handleKeyDown, true) + document.removeEventListener('keyup', handleKeyUp, true) + } + }, [recording, handleKeyDown, handleKeyUp]) + + const startRecording = () => { + setRecording(true) + setCurrentKeys([]) + } + + const clearShortcut = (e: React.MouseEvent) => { + e.stopPropagation() + onChange('') + } + + const label = action === 'mute' ? 'Mute' : 'Deafen' + + const renderKeyBadge = (key: string) => ( + + {key === 'ctrl' ? 'Ctrl' : key.charAt(0).toUpperCase() + key.slice(1)} + + ) + + const renderShortcutDisplay = (shortcut: string) => { + const keys = shortcut.split(' + ') + return ( +
+ {keys.map((key, i) => ( + + {i > 0 && +} + {renderKeyBadge(key)} + + ))} +
+ ) + } + + return ( +
+ {label} +
+ {recording ? ( +
+
+ + {currentKeys.length > 0 + ? renderShortcutDisplay(formatShortcut(currentKeys)) + : 'Press keys...'} + +
+ ) : value ? ( +
+ {renderShortcutDisplay(value)} + +
+ ) : ( + + )} +
+
+ ) +} + +interface ShortcutSettingsProps { + className?: string +} + +export function ShortcutSettings({ className }: ShortcutSettingsProps) { + const [shortcuts, setShortcuts] = useState({ mute: '', deafen: '' }) + const [isLoaded, setIsLoaded] = useState(false) + + useEffect(() => { + const loaded = loadShortcuts() + setShortcuts(loaded) + setIsLoaded(true) + }, []) + + const handleChange = useCallback((action: 'mute' | 'deafen', value: string) => { + setShortcuts(prev => { + const updated = { ...prev, [action]: value } + saveShortcuts(updated) + return updated + }) + if (value) { + toast.success(`${action === 'mute' ? 'Mute' : 'Deafen'} shortcut updated`) + } + }, []) + + const handleReset = () => { + const defaults = { mute: '', deafen: '' } + setShortcuts(defaults) + saveShortcuts(defaults) + toast.success('Shortcuts reset to defaults') + } + + if (!isLoaded) { + return null + } + + return ( +
+
+
+ +

Keyboard Shortcuts

+
+ +
+ +
+ handleChange('mute', value)} + otherShortcut={shortcuts.deafen} + /> + handleChange('deafen', value)} + otherShortcut={shortcuts.mute} + /> +
+ +

+ Press Escape to cancel recording. Shortcuts are disabled when typing in input fields. +

+
+ ) +} + +export default ShortcutSettings \ No newline at end of file diff --git a/apps/frontend/src/hooks/useKeyboardShortcuts.ts b/apps/frontend/src/hooks/useKeyboardShortcuts.ts new file mode 100644 index 0000000..01a20f1 --- /dev/null +++ b/apps/frontend/src/hooks/useKeyboardShortcuts.ts @@ -0,0 +1,71 @@ +'use client' + +import { useEffect, useCallback, useRef } from 'react' +import { + applyGlobalMuteToggle, + toggleSpeakerOutput +} from '@/app/lib/session-runtime' +import { + loadShortcuts, + parseShortcutString, + shortcutsEqual, + normalizeKey, + MODIFIER_KEYS, +} from '@/lib/keyboard-shortcuts' + +const IGNORE_TAGS = new Set(['INPUT', 'TEXTAREA']) + +function isInputElement(element: Element | null): boolean { + if (!element) return false + if (IGNORE_TAGS.has(element.tagName)) return true + if (element.getAttribute('contenteditable') === 'true') return true + return false +} + +export function useKeyboardShortcuts() { + const shortcutsRef = useRef(loadShortcuts()) + + const handleKeyDown = useCallback((e: KeyboardEvent) => { + if (isInputElement(document.activeElement)) return + + const pressed: string[] = [] + if (e.ctrlKey || e.metaKey) pressed.push('ctrl') + if (e.shiftKey) pressed.push('shift') + if (e.altKey) pressed.push('alt') + if (e.metaKey) pressed.push('meta') + + const key = normalizeKey(e.key) + if (!MODIFIER_KEYS.has(key)) { + pressed.push(key) + } + + if (pressed.length === 0) return + + const shortcuts = shortcutsRef.current + const muteKeys = parseShortcutString(shortcuts.mute) + const deafenKeys = parseShortcutString(shortcuts.deafen) + + if (muteKeys.length > 0 && shortcutsEqual(pressed, muteKeys)) { + e.preventDefault() + e.stopPropagation() + void applyGlobalMuteToggle() + return + } + + if (deafenKeys.length > 0 && shortcutsEqual(pressed, deafenKeys)) { + e.preventDefault() + e.stopPropagation() + toggleSpeakerOutput() + return + } + }, []) + + useEffect(() => { + document.addEventListener('keydown', handleKeyDown, true) + return () => { + document.removeEventListener('keydown', handleKeyDown, true) + } + }, [handleKeyDown]) + + return shortcutsRef +} \ No newline at end of file diff --git a/apps/frontend/src/lib/keyboard-shortcuts.ts b/apps/frontend/src/lib/keyboard-shortcuts.ts new file mode 100644 index 0000000..3c9cdef --- /dev/null +++ b/apps/frontend/src/lib/keyboard-shortcuts.ts @@ -0,0 +1,90 @@ +'use client' + +export type ShortcutAction = 'mute' | 'deafen' + +export interface KeyboardShortcut { + action: ShortcutAction + keys: string[] +} + +export const STORAGE_KEY = 'openchat-keyboard-shortcuts' + +export const MODIFIER_KEYS = new Set(['control', 'shift', 'alt', 'meta', 'ctrl']) + +export function normalizeKey(key: string): string { + const lower = key.toLowerCase() + const keyMap: Record = { + 'control': 'ctrl', + 'escape': 'esc', + 'arrowup': 'up', + 'arrowdown': 'down', + 'arrowleft': 'left', + 'arrowright': 'right', + ' ': 'space', + } + return keyMap[lower] || lower +} + +export function isValidShortcut(keys: string[]): boolean { + const normalized = keys.map(normalizeKey) + const nonModifiers = normalized.filter(k => !MODIFIER_KEYS.has(k)) + return nonModifiers.length >= 1 +} + +export function formatShortcut(keys: string[]): string { + const order = ['ctrl', 'alt', 'shift', 'meta'] + const normalized = keys.map(normalizeKey) + + const modifiers = normalized.filter(k => MODIFIER_KEYS.has(k)).sort((a, b) => order.indexOf(a) - order.indexOf(b)) + const nonModifiers = normalized.filter(k => !MODIFIER_KEYS.has(k)) + + return [...modifiers, ...nonModifiers].join(' + ') +} + +export function parseShortcutString(shortcut: string): string[] { + return shortcut.split(' + ').map(normalizeKey) +} + +export function shortcutsEqual(a: string[], b: string[]): boolean { + const normalize = (keys: string[]) => keys.map(normalizeKey).sort() + return JSON.stringify(normalize(a)) === JSON.stringify(normalize(b)) +} + +export interface StoredShortcuts { + mute: string + deafen: string +} + +export function loadShortcuts(): StoredShortcuts { + if (typeof window === 'undefined') { + return { mute: '', deafen: '' } + } + + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (stored) { + return JSON.parse(stored) + } + } catch (e) { + console.error('[KeyboardShortcuts] Failed to load shortcuts:', e) + } + + return { mute: '', deafen: '' } +} + +export function saveShortcuts(shortcuts: StoredShortcuts): void { + if (typeof window === 'undefined') return + + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(shortcuts)) + } catch (e) { + console.error('[KeyboardShortcuts] Failed to save shortcuts:', e) + } +} + +export function hasConflict(newKeys: string[], existing: StoredShortcuts): boolean { + const normalized = newKeys.map(normalizeKey) + const existingKeys = [parseShortcutString(existing.mute), parseShortcutString(existing.deafen)] + + return existingKeys.some(keys => keys.length > 0 && shortcutsEqual(normalized, keys)) +} \ No newline at end of file