diff --git a/.env.example b/.env.example index bf83588..97d57fe 100644 --- a/.env.example +++ b/.env.example @@ -215,8 +215,17 @@ DEEPSQL_BACKEND_PORT=8080 DEEPSQL_POSTGRES_PORT=5432 DEEPSQL_VALKEY_PORT=6379 -# Browser origins allowed to call the backend -CORS_ALLOWED_ORIGINS=http://localhost:3000 +# Browser origins allowed to call the backend. +# +# This REPLACES the built-in list — it does not add to it. When you put your own +# hostname here, keep the loopback patterns too. The desktop client reaches a VM +# over an SSH tunnel and therefore serves the app from http://127.0.0.1:, +# with a port picked at runtime; if that origin is not allowed the app loads +# normally and then every login fails with a bare "403 Invalid CORS request". +# The `*` is a port wildcard (SecurityConfig uses setAllowedOriginPatterns). +# +# CORS_ALLOWED_ORIGINS=https://deepsql.example.com,http://127.0.0.1:*,http://localhost:* +CORS_ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:*,http://localhost:* # ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ # OPTIONAL — Email / SMTP diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml new file mode 100644 index 0000000..42799aa --- /dev/null +++ b/.github/workflows/desktop-release.yml @@ -0,0 +1,93 @@ +name: desktop-release + +# Builds the DeepSQL desktop client for every platform. Each OS builds its own +# targets on its own runner: cross-building Windows needs Wine and Linux targets +# need a matching glibc, and both are far less reliable than just using the +# native runner. +# +# Tag-triggered runs attach the installers to the GitHub release. Manual runs +# upload them as workflow artifacts, which is the easy way to hand a build to a +# colleague before there is a release to cut. + +on: + push: + tags: + - 'desktop-v*' + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - { os: macos-latest, name: macOS, target: '--mac' } + - { os: windows-latest, name: Windows, target: '--win' } + - { os: ubuntu-latest, name: Linux, target: '--linux' } + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: 22 + cache: npm + cache-dependency-path: desktop/package-lock.json + + - name: Install dependencies + working-directory: desktop + run: npm ci + + - name: Self-test the SSH tunnel transport + # Runs a real SSH server in-process, so it needs a display-free Electron. + # xvfb is only required on Linux; the other runners have a window server. + working-directory: desktop + run: ${{ matrix.os == 'ubuntu-latest' && 'xvfb-run --auto-servernum npm run selftest:tunnel' || 'npm run selftest:tunnel' }} + shell: bash + + - name: Build installers + working-directory: desktop + env: + # Signing is skipped when these are unset; electron-builder warns and + # produces unsigned artifacts rather than failing the build. + CSC_LINK: ${{ secrets.DESKTOP_CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.DESKTOP_CSC_KEY_PASSWORD }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + run: npx electron-builder ${{ matrix.target }} --publish never + + - uses: actions/upload-artifact@v4 + with: + name: deepsql-desktop-${{ matrix.name }} + if-no-files-found: error + path: | + desktop/release/*.dmg + desktop/release/*.zip + desktop/release/*.exe + desktop/release/*.AppImage + desktop/release/*.deb + desktop/release/*.rpm + + release: + name: attach to release + needs: build + if: startsWith(github.ref, 'refs/tags/desktop-v') + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/download-artifact@v4 + with: + path: artifacts + merge-multiple: true + + - uses: softprops/action-gh-release@v2 + with: + files: artifacts/* + fail_on_unmatched_files: true diff --git a/AGENTS.md b/AGENTS.md index 342b4e3..7d5e9f0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -110,6 +110,27 @@ User Message → ChatController → SpringAIChatService - **UI state**: Zustand stores with selector hooks (`useActiveTab`, `useDashboardActions`) - **Independent chat threads**: Per-tab, per-connection, stored in localStorage +## Desktop Client (Electron) + +`desktop/` is a standalone Electron app (its own `package.json`, not part of the +root npm project). It is a **thin client**: it never bundles the React frontend, +it navigates a `WebContentsView` at the real DeepSQL origin, so the UI is always +the version the server runs. Two transports resolve to that origin — direct TLS, +or an in-process SSH local forward (`ssh2`, no `ssh` binary needed). + +| Path | Purpose | +|------|---------| +| `desktop/src/main/transport.js` | Transport manager: connect/disconnect/health per profile | +| `desktop/src/main/tunnel.js` | SSH local forward, host-key TOFU-then-strict, auto-reconnect | +| `desktop/src/main/tls.js` | Cert policy (system / pinned / custom CA / TOFU) for Node **and** Chromium | +| `desktop/src/main/profiles.js` | Connection profiles; secrets only as `safeStorage` ciphertext | +| `desktop/src/main/windows/workspace.js` | Frameless shell: native chrome + embedded DeepSQL view | +| `desktop/src/renderer/shared/theme.css` | Mirrors `src/index.css` tokens — keep in step | + +No backend change was needed: `docker/nginx/default.conf` already serves the SPA, +`/api` and `/agent-api` from one origin, which is what makes the thin-client +model work without CORS or cookie special-casing. + ## Performance & Safety Guardrails - Log size cap (500MB) via stream wrappers diff --git a/CLAUDE.md b/CLAUDE.md index 572a050..3db6c8f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -93,6 +93,88 @@ mcp/ # DeepSQL Phase 1 MCP server (Node stdio wrapper around back agent/ # DeepSQL Agent customization (persona, skills, skins; customized Hermes runtime) ``` +## Desktop Client (`desktop/`) + +Cross-platform Electron client for a self-hosted DeepSQL VM. **Separate npm +project** — `cd desktop && npm install`, not part of the root `package.json`. + +```bash +cd desktop +npm start # run npm run dev # run with DevTools +npm run dist:mac # dmg + zip (arm64 + x64), also :win / :linux +npm run smoke -- --url https://deepsql.example.com # headless connection check +npm run selftest:tunnel # end-to-end SSH tunnel test (in-process SSH server) +``` + +**It is a thin client and deliberately does not bundle the React frontend.** It +navigates a `WebContentsView` at the real DeepSQL origin, so the UI is always the +version the VM is running — no bundle/backend skew, and no second copy of 40+ +tabs to maintain. `docker/nginx/default.conf` already serves the SPA, `/api` and +`/agent-api` from one origin, so cookies and SSE behave exactly as in a browser. +Do not "improve" this by bundling `dist/` — that reintroduces `SameSite` and +version-skew problems the current design does not have. + +**It needs exactly one piece of backend configuration, and CORS is it.** The +"zero backend changes" claim that used to sit here was wrong, and cost a long +debugging session. Over a tunnel the origin is `http://127.0.0.1:`, +not the VM's hostname, so a deployment whose `CORS_ALLOWED_ORIGINS` names only +its public hostname rejects the desktop client. The failure is maximally +misleading: Chromium omits `Origin` on same-origin GETs, so the health probe, +the SPA and every read succeed, and the *first POST* — the login — comes back +`403` with the plain-text body `Invalid CORS request`. That body has no +`message` field, so `client.js`'s axios interceptor falls through to axios's own +wording and the user sees **"Request failed with status code 403"**, which names +neither CORS nor the origin. Fix: keep loopback patterns in the allowlist — +`CORS_ALLOWED_ORIGINS=https://your-host,http://127.0.0.1:*,http://localhost:*`. +Port wildcards work only because `SecurityConfig` uses +`setAllowedOriginPatterns`; `setAllowedOrigins` would reject `*` alongside +`allowCredentials(true)`. `probe.js` now sends an `Origin` header for exactly +this reason, so the rejection is caught at connect time and named. + +**Two transports, one abstraction.** Both resolve to an *origin*, so nothing +downstream of `desktop/src/main/transport.js` knows which is in use: + +- **Direct TLS** — the VM's HTTPS origin. Four certificate modes (`system`, + `pinned`, `custom-ca`, `insecure`/TOFU), applied to **both** the Node health + probe and the Chromium session (`tls.applyToSession`). Applying it to only one + gives a connection that tests green but renders a certificate error. +- **SSH tunnel** — `ssh2` local forward, loopback-bound, no `ssh` binary needed. + The local port is *sticky* across launches on purpose: the origin includes the + port, and a fresh random port would silently reset the web app's + `localStorage`. `http://127.0.0.1:*` is a Chromium secure context, so the + backend's `Secure` cookies still work over the tunnel. **Forward to the + frontend container (3000), not a host reverse proxy on :80** — that proxy + matches on `server_name`, a tunnel arrives with `Host: 127.0.0.1:`, + and the request lands on the default vhost as a 404 that reads like a broken + backend. The container's nginx uses `server_name _` and answers any Host. + +Three non-obvious things, all found the hard way: + +1. **`Client.connect({ privateKey })` must get the raw key material, not the + object `sshUtils.parseKey` returns.** Handed a parsed key, ssh2 silently + never offers the publickey method and the server replies with a bare + authentication failure — a symptom that points at the VM's `authorized_keys` + rather than at a type mismatch on our side. `loadPrivateKey` parses only to + produce good error messages and returns the buffer. +2. **Authentication succeeding says nothing about forwarding being allowed.** + A hardened sshd (`AllowTcpForwarding no`) accepts the login and refuses every + `direct-tcpip` channel; the failure otherwise surfaces as "socket hang up" on + the first browser request, pointing nowhere near sshd. `verifyForwarding()` + opens and closes one channel right after auth and classifies the refusal by + SSH reason code — 1 (`ADMINISTRATIVELY_PROHIBITED`, verified against real + OpenSSH) names `AllowTcpForwarding`, 2 (`CONNECT_FAILED`) means nothing is + listening on the remote port. +3. **Only a session that once reached `ready` may be reconnected.** Gating + reconnects on `everReady` is what stops a connect that fails on + authentication from retrying forever behind a caller that already surfaced + the error. + +Secrets (key passphrases, SSH passwords) are stored as `safeStorage` ciphertext; +where no OS keychain exists nothing is written to disk and the launcher says so. +Each profile gets its own session partition, so two DeepSQL servers never share +cookies. `.github/workflows/desktop-release.yml` builds all three platforms on +their native runners. See `desktop/README.md` for the full picture. + ## MCP Server - `mcp/deepsql-phase1-server.js` implements a Phase 1 stdio MCP server for internal rollout. diff --git a/backend/src/main/resources/application-prod.properties b/backend/src/main/resources/application-prod.properties index d4d056f..b6d8fac 100644 --- a/backend/src/main/resources/application-prod.properties +++ b/backend/src/main/resources/application-prod.properties @@ -57,8 +57,11 @@ app.base-url=${APP_BASE_URL:http://localhost:3000} # CORS Configuration # Override CORS_ALLOWED_ORIGINS with the origin(s) your frontend is -# actually served from. The default below covers local dev only. -cors.allowed.origins=${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001} +# actually served from, and keep the loopback patterns: the desktop +# client's SSH tunnel serves the app from http://127.0.0.1:, +# so dropping them rejects every tunnel login with 403 "Invalid CORS +# request". See the longer note in application.properties. +cors.allowed.origins=${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001,http://127.0.0.1:*,http://localhost:*} # File upload limits (slow query logs can be large) spring.servlet.multipart.max-file-size=2048MB diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 672453a..c3a55f7 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -81,10 +81,22 @@ db-scheduler.shutdown-max-wait=45m db-scheduler.immediate-execution-enabled=true # CORS Configuration -# Localhost only by default so `mvn spring-boot:run` + `npm run dev` works out of the box. +# Loopback only by default so `mvn spring-boot:run` + `npm run dev` works out of the box. # Any deployment serving a browser from another host must set CORS_ALLOWED_ORIGINS. -# Note: exact origins (no patterns) for reliability. -cors.allowed.origins=${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001,http://localhost:3002,http://127.0.0.1:3000,http://127.0.0.1:3001,http://127.0.0.1:3002} +# +# The loopback entries carry a port wildcard because the desktop client's SSH tunnel +# serves the app from http://127.0.0.1:, and that port is chosen at runtime. +# Enumerating ports here is what made every tunnel connection fail with a bare +# "403 Invalid CORS request": Spring treats any request carrying Origin as cross-origin +# (the same-origin short-circuit went away in 5.3), so an unlisted loopback port is +# rejected. SecurityConfig uses setAllowedOriginPatterns, so `*` is legal in the port +# position and stays compatible with allowCredentials(true) — plain setAllowedOrigins +# would not be. +# +# IMPORTANT: overriding CORS_ALLOWED_ORIGINS replaces this list wholesale. A deployment +# that sets it to its public hostname alone drops the loopback entries and breaks the +# desktop client. Keep the loopback patterns alongside your hostname. +cors.allowed.origins=${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:3001,http://localhost:3002,http://127.0.0.1:3000,http://127.0.0.1:3001,http://127.0.0.1:3002,http://127.0.0.1:*,http://localhost:*} # File upload limits (slow query logs can be large) spring.servlet.multipart.max-file-size=2048MB diff --git a/desktop/.gitignore b/desktop/.gitignore new file mode 100644 index 0000000..dad03f4 --- /dev/null +++ b/desktop/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +release/ +build/icon.iconset/ +*.log diff --git a/desktop/README.md b/desktop/README.md new file mode 100644 index 0000000..8b0505f --- /dev/null +++ b/desktop/README.md @@ -0,0 +1,259 @@ +# DeepSQL Desktop + +A cross-platform desktop client for a self-hosted DeepSQL server. It connects to +the VM (or bare metal) running the DeepSQL stack either **directly over TLS** or +through an **SSH tunnel**, and presents the DeepSQL UI in a native window with +connection management, health, and transport status built into the chrome. + +``` +┌────────────────── DeepSQL.app ──────────────────┐ +│ Launcher Workspace window │ +│ (connections) ┌──────────────────────────┐ │ +│ │ native chrome ● 170 ms │ │ +│ ├──────────────────────────┤ │ +│ │ the DeepSQL web UI │ │ +│ │ (WebContentsView) │ │ +│ └──────────────────────────┘ │ +└──────────┬───────────────────────┬───────────────┘ + │ TLS │ SSH tunnel + ▼ ▼ + https://deepsql.example.com 127.0.0.1:PORT ⇢ ssh ⇢ VM:3000 +``` + +## Design: a thin client, not a second frontend + +The client **does not bundle a copy of the DeepSQL web app**. It navigates an +embedded `WebContentsView` at the real DeepSQL origin. Consequences worth +knowing: + +- The UI is always the exact version the VM is running. There is no + bundle/backend skew and no second copy of 40+ tabs to keep in step. +- Cookies and SSE behave exactly as in a browser, because the app is served + from one origin — `docker/nginx/default.conf` already fronts `/api` and + `/agent-api` behind the frontend container. +- **CORS is the one exception, and the one backend setting you must get right.** + Over a tunnel that origin is `http://127.0.0.1:`, so the VM's + `CORS_ALLOWED_ORIGINS` has to allow it. See + [CORS on the VM](#cors-on-the-vm-the-403-nobody-can-read). +- Everything the client adds is what a browser tab cannot show: which VM you + are on, how you are reaching it, and whether that path is healthy. + +Both transports resolve to the same thing — an origin — so nothing downstream +of the transport layer knows or cares which one is in use. + +## Install + +### From a release build + +| Platform | Artifact | +| --- | --- | +| macOS (Apple Silicon / Intel) | `DeepSQL--arm64.dmg`, `-x64.dmg` | +| Windows | `DeepSQL Setup .exe` (NSIS), portable `.exe` | +| Linux | `.AppImage`, `.deb`, `.rpm` | + +macOS builds are unsigned unless you supply signing credentials, so the first +launch needs **right-click → Open** (or `xattr -dr com.apple.quarantine +/Applications/DeepSQL.app`). + +### From source + +```bash +cd desktop +npm install +npm start # run the app +npm run dev # run with DevTools open +``` + +## Building installers + +```bash +npm run icons # regenerate build/icon.png from build/icon.svg +npm run dist:mac # dmg + zip, arm64 and x64 +npm run dist:win # nsis + portable, x64 and arm64 +npm run dist:linux # AppImage + deb + rpm +npm run dist # every target this host can build +``` + +Artifacts land in `desktop/release/`. Cross-building is limited by the host: +Windows targets need Wine on macOS/Linux, and Linux targets are most reliable in +the `electron-builder` Docker image. Building each platform on its own CI runner +is the low-friction option. + +**Code signing** is off by default. Set the standard electron-builder +environment variables to enable it: + +- macOS: `CSC_LINK`, `CSC_KEY_PASSWORD`, plus `APPLE_ID`, `APPLE_APP_SPECIFIC_PASSWORD`, `APPLE_TEAM_ID` for notarisation. +- Windows: `CSC_LINK`, `CSC_KEY_PASSWORD` (or an Azure Trusted Signing config). + +**Auto-update** is opt-in. `package.json` sets `"publish": null`, so no update +feed is baked in and the updater no-ops. To enable it, either set a `publish` +target (GitHub Releases, S3, generic) before building, or point +`DEEPSQL_UPDATE_FEED` at a generic feed URL at runtime. + +## Connecting + +### Direct over TLS + +Enter the origin that serves the DeepSQL UI — the same URL you would open in a +browser. Certificate verification has four modes: + +| Mode | Use when | +| --- | --- | +| **Publicly trusted** | The VM has a real hostname and a Let's Encrypt / commercial certificate. Default. | +| **Pinned certificate** | Self-signed certificate, or the server is only reachable by IP. Leave the fingerprint blank to pin whatever is presented on the first connection. | +| **Private CA bundle** | Corporate internal PKI. Point it at the CA `.pem`. | +| **Trust on first use** | Bootstrapping only. Accepts any certificate once, then pins it. Shown in red, and the workspace chrome badges the connection `UNVERIFIED`. | + +The pin applies to both the health probe (Node) and the embedded browser +(Chromium), so the UI is held to exactly the same rule as the connection check. + +### SSH tunnel + +Equivalent to `ssh -i key.pem -L :127.0.0.1:3000 ubuntu@vm`, but run +in-process — no `ssh` binary required, which matters on Windows. + +Use it when the DeepSQL ports are closed to the internet. The Compose stack only +publishes to the VM's own host, so a tunnel reaches them without exposing +anything publicly. + +- **Remote port** is DeepSQL *as seen from inside the VM*. Use `3000`, the + frontend container. Pointing it at a host reverse proxy on `:80` usually + returns 404: that proxy matches on `server_name`, and a tunnel arrives with + `Host: 127.0.0.1:`, which matches no vhost. The container's own nginx + uses `server_name _` and answers whatever Host it is given. +- **Local port** `0` picks a free port and then reuses it on later launches, so + the origin (and therefore the web app's `localStorage`) stays stable. +- The listener binds `127.0.0.1` only — never the LAN. +- Host keys are **trust on first use, then strict**. A changed host key aborts + the connection with both fingerprints shown rather than offering a dialog to + click through. If the VM was genuinely rebuilt, clear the pinned key in the + connection settings. + +`http://127.0.0.1:` is a secure context in Chromium, so the backend's +`Secure` session cookies are still accepted over the tunnel. + +### CORS on the VM (the 403 nobody can read) + +The tunnel gives the web app the origin `http://127.0.0.1:`, not your VM's +hostname. If `CORS_ALLOWED_ORIGINS` on the VM names only the hostname, the +backend rejects that origin — and it does so in the most confusing way +available: + +- Chromium omits `Origin` on same-origin **GET**s, so the health probe, the SPA + and every read work. The connection tests green. +- Chromium *does* send `Origin` on same-origin **POST/PUT/DELETE**, and Spring + treats any request carrying `Origin` as cross-origin (the same-origin + short-circuit was removed in Spring 5.3). So the first POST — the login — + returns `403` with the plain-text body `Invalid CORS request`. +- That body has no `message` field, so the web app's axios interceptor falls + back to axios's own wording: **"Request failed with status code 403"**, naming + neither CORS nor the origin. + +Fix it on the VM by keeping loopback patterns in the allowlist alongside your +hostname, then restarting the backend: + +```bash +# /home//deepsql-self-host/.env +CORS_ALLOWED_ORIGINS=https://deepsql.example.com,http://127.0.0.1:*,http://localhost:* + +docker compose up -d backend +``` + +The `*` is a **port** wildcard, which matters because the tunnel's local port is +chosen at runtime; enumerating ports means re-editing the VM whenever it +changes. It is legal only because `SecurityConfig` uses +`setAllowedOriginPatterns` — plain `setAllowedOrigins` rejects `*` in +combination with `allowCredentials(true)`. Note that `CORS_ALLOWED_ORIGINS` +*replaces* the built-in list rather than extending it, which is how the loopback +entries usually go missing. + +Verify without opening the app: + +```bash +curl -s -o /dev/null -w '%{http_code}\n' \ + -H "Origin: http://127.0.0.1:" http://127.0.0.1:/api/actuator/health +# 200 = allowed · 403 = still missing from CORS_ALLOWED_ORIGINS +``` + +Since the probe now sends an `Origin` header, the launcher catches this at +connect time and says so, instead of letting it surface as a failed login. + +## Security model + +- **Secrets** (key passphrases, SSH passwords) are encrypted with Electron + `safeStorage`, backed by Keychain / DPAPI / libsecret. On a machine with no + usable secret service, nothing is written to disk and the secret is kept in + memory for the session only — the launcher says so rather than pretending. +- **Session isolation**: each connection gets its own persistent partition + (`persist:deepsql-`), so signing into two DeepSQL servers never + crosses cookies or cached state. +- **Renderer hardening**: `contextIsolation` on, `nodeIntegration` off, the + content view sandboxed, `webview` attachment blocked, and permissions denied + by default except sanitised clipboard writes. +- **Navigation confinement**: the embedded view cannot leave the DeepSQL origin. + Same-origin popups (shared dashboards) open in a window on the same session; + everything else goes to the OS browser. +- **No credential proxying**: the client never sees database credentials. It + speaks to DeepSQL's own API surface exactly as a browser does. + +## Deep links + +`deepsql://connect?url=https://deepsql.example.com&name=Production` opens the +launcher with a connection pre-filled — a one-click onboarding link for an admin +to paste into internal docs. `transport=tunnel`, `sshHost` and `sshUser` are also +accepted. + +## Diagnostics + +```bash +# Check a server the same way the app does, without opening a window +npm run smoke -- --url https://deepsql.example.com +npm run smoke -- --ssh-host 20.29.48.144 --ssh-user ubuntu --key ~/keys/vm.pem + +# Exercise the tunnel end to end against a throwaway in-process SSH server +npm run selftest:tunnel +``` + +Logs are at `/logs/desktop.log`; the launcher footer has an **Open log +file** link. Connection profiles live in `/profiles.json` (mode 0600, +secrets stored only as `safeStorage` ciphertext). + +| Platform | userData | +| --- | --- | +| macOS | `~/Library/Application Support/DeepSQL` | +| Windows | `%APPDATA%\DeepSQL` | +| Linux | `~/.config/DeepSQL` | + +## Layout + +``` +src/ + main/ + index.js app lifecycle, single instance, deep links + config.js shared constants + profiles.js connection profiles + persistence + secrets.js safeStorage wrapper + transport.js transport manager (connect/disconnect/health) + tunnel.js SSH local forward (ssh2) + tls.js certificate policy for Node and Chromium + probe.js /api/actuator/health reachability check + ipc.js every renderer→main entry point + menu.js, updater.js, logger.js + windows/ + launcher.js connection manager window + workspace.js frameless shell: native chrome + DeepSQL view + preload/ narrow contextBridge APIs + renderer/ + shared/theme.css DeepSQL design tokens (mirrors src/index.css) + launcher/ connection manager UI + chrome/ workspace top chrome +scripts/ + generate-icons.js SVG → build/icon.png via Electron + smoke.js headless connection check + tunnel-selftest.js end-to-end SSH tunnel test +``` + +The renderers are plain HTML/CSS/JS with no build step: they are chrome around +an IPC surface, and the DeepSQL React app they wrap is loaded from the server, +not reimplemented. `renderer/shared/theme.css` mirrors the tokens in the web +app's `src/index.css` — keep the two in step if the palette moves. diff --git a/desktop/build/entitlements.mac.plist b/desktop/build/entitlements.mac.plist new file mode 100644 index 0000000..8078085 --- /dev/null +++ b/desktop/build/entitlements.mac.plist @@ -0,0 +1,22 @@ + + + + + + com.apple.security.cs.allow-jit + + com.apple.security.cs.allow-unsigned-executable-memory + + com.apple.security.cs.disable-library-validation + + + com.apple.security.network.client + + + com.apple.security.network.server + + + com.apple.security.files.user-selected.read-only + + + diff --git a/desktop/build/icon.png b/desktop/build/icon.png new file mode 100644 index 0000000..109c8c8 Binary files /dev/null and b/desktop/build/icon.png differ diff --git a/desktop/build/icon.svg b/desktop/build/icon.svg new file mode 100644 index 0000000..683bd0d --- /dev/null +++ b/desktop/build/icon.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/desktop/package-lock.json b/desktop/package-lock.json new file mode 100644 index 0000000..f0b6404 --- /dev/null +++ b/desktop/package-lock.json @@ -0,0 +1,3712 @@ +{ + "name": "deepsql-desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "deepsql-desktop", + "version": "0.1.0", + "license": "Apache-2.0", + "dependencies": { + "electron-updater": "^6.8.9", + "ssh2": "^1.17.0" + }, + "devDependencies": { + "electron": "^43.2.0", + "electron-builder": "^26.15.3" + } + }, + "node_modules/@electron-internal/extract-zip": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@electron-internal/extract-zip/-/extract-zip-1.0.5.tgz", + "integrity": "sha512-+bqFCP98pLI0Tt0XQo1TmlXtwjWchISndDOxCkEcIuUgXWpBnLyRI+2DU+mesvnMMX6L1XDqYNA0lXNDHd/yiA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/asar": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@electron/asar/-/asar-3.4.1.tgz", + "integrity": "sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@electron/asar/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/asar/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@electron/asar/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@electron/fuses": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz", + "integrity": "sha512-zx0EIq78WlY/lBb1uXlziZmDZI4ubcCXIMJ4uGjXzZW0nS19TjSPeXPAjzzTmKQlJUZm0SbmZhPKP7tuQ1SsEw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.1", + "fs-extra": "^9.0.1", + "minimist": "^1.2.5" + }, + "bin": { + "electron-fuses": "dist/bin.js" + } + }, + "node_modules/@electron/fuses/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/get": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-5.1.0.tgz", + "integrity": "sha512-3kSBtG8ObcTVfXanm5vVJ6UnBLEVmVsRk1M+vGqCuMBV+XLCbJYuWQful+yIy0GQDsSlK0kHEriEHn7SPk4EnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^3.0.0", + "graceful-fs": "^4.2.11", + "progress": "^2.0.3", + "semver": "^7.6.3", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=22.12.0" + }, + "optionalDependencies": { + "undici": "^7.24.4" + } + }, + "node_modules/@electron/notarize": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@electron/notarize/-/notarize-2.5.0.tgz", + "integrity": "sha512-jNT8nwH1f9X5GEITXaQ8IF/KdskvIkOFfB2CvwumsveVidzpSc+mvhhTMdAGSYF3O+Nq49lJ7y+ssODRXu06+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.1", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@electron/notarize/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/osx-sign": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@electron/osx-sign/-/osx-sign-1.3.3.tgz", + "integrity": "sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "compare-version": "^0.1.2", + "debug": "^4.3.4", + "fs-extra": "^10.0.0", + "isbinaryfile": "^4.0.8", + "minimist": "^1.2.6", + "plist": "^3.0.5" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@electron/osx-sign/node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/@electron/rebuild": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@electron/rebuild/-/rebuild-4.2.0.tgz", + "integrity": "sha512-RKL/O+jGoXJMxrx/5771y1n0xTKmFuOYGO3gMmwypBM6rsH0kou0mswwdXA2JrhIkE4xyC7v9vGk0n6NPzgOxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.1.1", + "node-abi": "^4.2.0", + "node-api-version": "^0.2.1", + "node-gyp": "^12.2.0", + "read-binary-file-arch": "^1.0.6" + }, + "bin": { + "electron-rebuild": "lib/cli.js" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/@electron/universal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-2.0.3.tgz", + "integrity": "sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@electron/universal/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@electron/universal/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/universal/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", + "integrity": "sha512-1DpKU0Z5ThltBwjNySMC14g0CkbyhCaz9FkhxqNsZI6uAPJXFS8cMXlBKo26FJ8ZuW6S9GCMcR9IO5k2X5/9Fg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@noble/hashes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", + "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@peculiar/asn1-schema": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", + "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/json-schema": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@peculiar/json-schema/-/json-schema-1.1.12.tgz", + "integrity": "sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@peculiar/utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@peculiar/utils/-/utils-2.0.3.tgz", + "integrity": "sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/@peculiar/webcrypto": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.7.1.tgz", + "integrity": "sha512-ODOov0sGMJMf3jPonOkgGqPknTsu+DdQ7kD++gz8aI+aFMOMHFbWAA2taqXXVTdP+OTOQR/znGvSpmkeI0WTYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "tslib": "^2.8.1", + "webcrypto-core": "^1.9.2" + }, + "engines": { + "node": ">=14.18.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.13", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.13.tgz", + "integrity": "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/abbrev": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", + "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-lib": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", + "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron/asar": "3.4.1", + "@electron/fuses": "^1.8.0", + "@electron/get": "^3.0.0", + "@electron/notarize": "2.5.0", + "@electron/osx-sign": "1.3.3", + "@electron/rebuild": "^4.0.4", + "@electron/universal": "2.0.3", + "@malept/flatpak-bundler": "^0.4.0", + "@noble/hashes": "^2.2.0", + "@peculiar/webcrypto": "^1.7.1", + "@types/fs-extra": "9.0.13", + "ajv": "^8.18.0", + "asn1js": "^3.0.10", + "async-exit-hook": "^2.0.1", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chromium-pickle-js": "^0.2.0", + "ci-info": "4.3.1", + "debug": "^4.3.4", + "dotenv": "^16.4.5", + "dotenv-expand": "^11.0.6", + "ejs": "^3.1.8", + "electron-publish": "26.15.3", + "fs-extra": "^10.1.0", + "hosted-git-info": "^4.1.0", + "isbinaryfile": "^5.0.0", + "jiti": "^2.4.2", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "lazy-val": "^1.0.5", + "minimatch": "^10.2.5", + "pkijs": "^3.4.0", + "plist": "3.1.0", + "proper-lockfile": "^4.1.2", + "resedit": "^1.7.0", + "semver": "~7.7.3", + "tar": "^7.5.7", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0", + "unzipper": "^0.12.3", + "which": "^5.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "dmg-builder": "26.15.3", + "electron-builder-squirrel-windows": "26.15.3" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-3.1.0.tgz", + "integrity": "sha512-F+nKc0xW+kVbBRhFzaMgPy3KwmuNTYX1fx6+FxxoSnNgwYX6LD7AKBTWkU0MQ6IBoe7dz069CNkR673sPAgkCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/app-builder-lib/node_modules/@electron/get/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/app-builder-lib/node_modules/ci-info": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.3.1.tgz", + "integrity": "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/app-builder-lib/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/asn1js": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.10.tgz", + "integrity": "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.5", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buildcheck": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.7.tgz", + "integrity": "sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==", + "optional": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/builder-util": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", + "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "cross-spawn": "^7.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "js-yaml": "^4.1.0", + "sanitize-filename": "^1.6.3", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0", + "tiny-async-pool": "1.3.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.7.0", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.7.0.tgz", + "integrity": "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/bytestreamjs": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bytestreamjs/-/bytestreamjs-2.0.1.tgz", + "integrity": "sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chownr": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/cpu-features": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", + "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", + "hasInstallScript": true, + "optional": true, + "dependencies": { + "buildcheck": "~0.0.6", + "nan": "^2.19.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-4.2.0.tgz", + "integrity": "sha512-2xMCmOoMrdQIPHdsTawECdNPwlVFB9zGcz3kuhmBO6U3oU+UQjsue0i8ayLKpgBcm+hcXPMVSGUN9d+pvJ6+VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.5", + "p-limit": "^3.1.0 " + } + }, + "node_modules/dir-compare/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dir-compare/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", + "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "11.0.7", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz", + "integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dotenv": "^16.4.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "readable-stream": "^2.0.2" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "43.2.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-43.2.0.tgz", + "integrity": "sha512-80zvrgG7ZRXD+tD0IyLvrnN9n+veSxadMRsMaC9wKKP3iUbtC7rGM8+dVuCmOb0Rrwwv8ESW4awnUZh9Hbp1fA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@electron-internal/extract-zip": "^1.0.1", + "@electron/get": "^5.0.0", + "@types/node": "^24.9.0" + }, + "bin": { + "electron": "cli.js", + "install-electron": "install.js" + }, + "engines": { + "node": ">= 22.12.0" + } + }, + "node_modules/electron-builder": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", + "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "dmg-builder": "26.15.3", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "simple-update-notifier": "2.0.0", + "yargs": "^17.6.2" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder-squirrel-windows": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", + "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "app-builder-lib": "26.15.3", + "builder-util": "26.15.3", + "electron-winstaller": "5.4.0" + } + }, + "node_modules/electron-publish": { + "version": "26.15.3", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", + "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "aws4": "^1.13.2", + "builder-util": "26.15.3", + "builder-util-runtime": "9.7.0", + "chalk": "^4.1.2", + "form-data": "^4.0.5", + "fs-extra": "^10.1.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-updater": { + "version": "6.8.9", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.8.9.tgz", + "integrity": "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==", + "license": "MIT", + "dependencies": { + "builder-util-runtime": "9.7.0", + "fs-extra": "^10.1.0", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "lodash.escaperegexp": "^4.1.2", + "lodash.isequal": "^4.5.0", + "semver": "~7.7.3", + "tiny-typed-emitter": "^2.1.0" + } + }, + "node_modules/electron-updater/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/electron-winstaller": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/electron-winstaller/-/electron-winstaller-5.4.0.tgz", + "integrity": "sha512-bO3y10YikuUwUuDUQRM4KfwNkKhnpVO7IPdbsrejwN9/AABJzzTQ4GeHwyzNSrVO+tEH3/Np255a3sVZpZDjvg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@electron/asar": "^3.2.1", + "debug": "^4.1.1", + "fs-extra": "^7.0.1", + "lodash": "^4.17.21", + "temp": "^0.9.0" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "@electron/windows-sign": "^1.1.2" + } + }, + "node_modules/electron-winstaller/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/electron-winstaller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "peer": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-winstaller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", + "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filelist": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", + "integrity": "sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.7.tgz", + "integrity": "sha512-gnWD14Jh3FzS3CPhF0AxNOJ8CxqeblPTADzI38r0wt8ZyQl5edpy75myt08EG2oKvpyiqSqsx+Wkz9vtkbTqYQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", + "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-yaml": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.escaperegexp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz", + "integrity": "sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minizlib": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "license": "MIT", + "optional": true + }, + "node_modules/node-abi": { + "version": "4.33.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.33.0.tgz", + "integrity": "sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.6.3" + }, + "engines": { + "node": ">=22.12.0" + } + }, + "node_modules/node-api-version": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-api-version/-/node-api-version-0.2.1.tgz", + "integrity": "sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + } + }, + "node_modules/node-gyp": { + "version": "12.4.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.4.0.tgz", + "integrity": "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "nopt": "^9.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5", + "tar": "^7.5.4", + "tinyglobby": "^0.2.12", + "undici": "^6.25.0", + "which": "^6.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", + "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=20" + } + }, + "node_modules/node-gyp/node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", + "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^4.0.0" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/nopt": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", + "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pe-library": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/pe-library/-/pe-library-0.4.1.tgz", + "integrity": "sha512-eRWB5LBz7PpDu4PUlwT0PhnQfTQJlDDdPa35urV4Osrm0t0AqQFGn+UIkU3klZvwJ8KPO3VbBFsXquA6p6kqZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkijs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pkijs/-/pkijs-3.4.0.tgz", + "integrity": "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@noble/hashes": "1.4.0", + "asn1js": "^3.0.6", + "bytestreamjs": "^2.0.1", + "pvtsutils": "^1.3.6", + "pvutils": "^1.1.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/pkijs/node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/proc-log": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", + "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proper-lockfile": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", + "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "retry": "^0.12.0", + "signal-exit": "^3.0.2" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/pvtsutils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", + "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.8.1" + } + }, + "node_modules/pvutils": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", + "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-binary-file-arch": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/read-binary-file-arch/-/read-binary-file-arch-1.0.6.tgz", + "integrity": "sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "bin": { + "read-binary-file-arch": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resedit": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/resedit/-/resedit-1.7.2.tgz", + "integrity": "sha512-vHjcY2MlAITJhC0eRD/Vv8Vlgmu9Sd3LX9zZvtGzU5ZImdTN3+d6e/4mnTyV8vEbyf1sgNIrWxhWlrys52OkEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pe-library": "^0.4.1" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jet2jet" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.4.tgz", + "integrity": "sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/ssh2": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.17.0.tgz", + "integrity": "sha512-wPldCk3asibAjQ/kziWQQt1Wh3PgDFpC0XpwclzKcdT1vql6KeYxf5LIt4nlFkUeR8WuphYMKqUA56X4rjbfgQ==", + "hasInstallScript": true, + "dependencies": { + "asn1": "^0.2.6", + "bcrypt-pbkdf": "^1.0.2" + }, + "engines": { + "node": ">=10.16.0" + }, + "optionalDependencies": { + "cpu-features": "~0.0.10", + "nan": "^2.23.0" + } + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar": { + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/tiny-async-pool": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.3.0.tgz", + "integrity": "sha512-01EAw5EDrcVrdgyCLgoSPvqznC0sVxDSVeiOz09FUpjh71G79VCqneOr+xvt7T1r76CF6ZZfPjHorN2+d+3mqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^5.5.0" + } + }, + "node_modules/tiny-async-pool/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tiny-typed-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz", + "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", + "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unzipper": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.12.5.tgz", + "integrity": "sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "~3.7.2", + "duplexer2": "~0.1.4", + "fs-extra": "11.3.1", + "graceful-fs": "^4.2.2", + "node-int64": "^0.4.0" + } + }, + "node_modules/unzipper/node_modules/fs-extra": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.1.tgz", + "integrity": "sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/webcrypto-core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.9.2.tgz", + "integrity": "sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@peculiar/asn1-schema": "^2.7.0", + "@peculiar/json-schema": "^1.1.12", + "@peculiar/utils": "^2.0.2", + "asn1js": "^3.0.10", + "tslib": "^2.8.1" + } + }, + "node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/desktop/package.json b/desktop/package.json new file mode 100644 index 0000000..52211c6 --- /dev/null +++ b/desktop/package.json @@ -0,0 +1,162 @@ +{ + "name": "deepsql-desktop", + "productName": "DeepSQL", + "version": "0.1.0", + "private": true, + "description": "DeepSQL desktop client — connect to a self-hosted DeepSQL VM over TLS or an SSH tunnel.", + "license": "Apache-2.0", + "author": { + "name": "DeepSQL", + "email": "support@deepsql.local" + }, + "type": "commonjs", + "main": "src/main/index.js", + "scripts": { + "start": "electron .", + "dev": "DEEPSQL_DESKTOP_DEV=1 electron .", + "icons": "electron scripts/generate-icons.js", + "pack": "electron-builder --dir", + "dist": "electron-builder", + "dist:mac": "electron-builder --mac", + "dist:win": "electron-builder --win", + "dist:linux": "electron-builder --linux", + "dist:all": "electron-builder --mac --win --linux", + "smoke": "electron scripts/smoke.js", + "selftest:tunnel": "electron scripts/tunnel-selftest.js" + }, + "dependencies": { + "electron-updater": "^6.8.9", + "ssh2": "^1.17.0" + }, + "devDependencies": { + "electron": "^43.2.0", + "electron-builder": "^26.15.3" + }, + "build": { + "appId": "com.deepsql.desktop", + "productName": "DeepSQL", + "copyright": "Copyright © DeepSQL", + "directories": { + "output": "release", + "buildResources": "build" + }, + "files": [ + "src/**/*", + "build/icon.png", + "!**/*.map" + ], + "protocols": [ + { + "name": "DeepSQL", + "schemes": [ + "deepsql" + ] + } + ], + "asar": true, + "mac": { + "category": "public.app-category.developer-tools", + "target": [ + { + "target": "dmg", + "arch": [ + "arm64", + "x64" + ] + }, + { + "target": "zip", + "arch": [ + "arm64", + "x64" + ] + } + ], + "icon": "build/icon.png", + "hardenedRuntime": true, + "gatekeeperAssess": false, + "entitlements": "build/entitlements.mac.plist", + "entitlementsInherit": "build/entitlements.mac.plist", + "extendInfo": { + "NSHumanReadableCopyright": "Copyright © DeepSQL" + } + }, + "dmg": { + "title": "DeepSQL ${version}", + "contents": [ + { + "x": 140, + "y": 200, + "type": "file" + }, + { + "x": 400, + "y": 200, + "type": "link", + "path": "/Applications" + } + ] + }, + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "portable", + "arch": [ + "x64" + ] + } + ], + "icon": "build/icon.png" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true, + "shortcutName": "DeepSQL" + }, + "linux": { + "target": [ + { + "target": "AppImage", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "deb", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "rpm", + "arch": [ + "x64" + ] + } + ], + "icon": "build/icon.png", + "category": "Development", + "maintainer": "DeepSQL ", + "desktop": { + "entry": { + "Name": "DeepSQL", + "Comment": "AI-powered Database Performance Assistant", + "MimeType": "x-scheme-handler/deepsql" + } + } + }, + "publish": null + } +} diff --git a/desktop/scripts/generate-icons.js b/desktop/scripts/generate-icons.js new file mode 100644 index 0000000..db26365 --- /dev/null +++ b/desktop/scripts/generate-icons.js @@ -0,0 +1,52 @@ +'use strict'; + +/** + * Rasterise build/icon.svg to build/icon.png (1024×1024). + * + * Run with `npm run icons`. It uses Electron's own Chromium rather than + * ImageMagick/rsvg so contributors on any OS can regenerate the icon with the + * dependencies already in this package. electron-builder derives the .icns and + * .ico it needs from that single PNG at package time. + */ + +const fs = require('node:fs'); +const path = require('node:path'); +const { app, BrowserWindow, nativeImage } = require('electron'); + +const SIZE = 1024; +const buildDir = path.join(__dirname, '..', 'build'); +const svgPath = path.join(buildDir, 'icon.svg'); +const pngPath = path.join(buildDir, 'icon.png'); + +app.disableHardwareAcceleration(); + +app.whenReady().then(async () => { + const svg = fs.readFileSync(svgPath, 'utf8'); + const page = ` + ${svg}`; + + const window = new BrowserWindow({ + width: SIZE, + height: SIZE, + show: false, + transparent: true, + frame: false, + webPreferences: { offscreen: true }, + }); + + await window.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(page)}`); + const image = await window.webContents.capturePage({ + x: 0, + y: 0, + width: SIZE, + height: SIZE, + }); + + const png = nativeImage.createFromBuffer(image.toPNG()).toPNG(); + fs.writeFileSync(pngPath, png); + process.stdout.write(`wrote ${pngPath} (${SIZE}x${SIZE}, ${png.length} bytes)\n`); + + window.destroy(); + app.quit(); +}); diff --git a/desktop/scripts/smoke.js b/desktop/scripts/smoke.js new file mode 100644 index 0000000..bb51b98 --- /dev/null +++ b/desktop/scripts/smoke.js @@ -0,0 +1,100 @@ +'use strict'; + +/** + * Headless connection check — the same transport code the app uses, without a + * window. Handy for diagnosing a VM from a terminal or CI before shipping a + * profile to users. + * + * npm run smoke -- --url https://deepsql.example.com + * npm run smoke -- --url https://10.0.0.5 --tls pinned + * npm run smoke -- --ssh-host 20.29.48.144 --ssh-user ubuntu \ + * --key ~/keys/vm.pem [--passphrase secret] [--remote-port 80] + * + * It runs against a throwaway userData directory so it never touches the + * profiles or keychain entries of the installed app. + */ + +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { app } = require('electron'); + +const tmpUserData = fs.mkdtempSync(path.join(os.tmpdir(), 'deepsql-smoke-')); +app.setPath('userData', tmpUserData); + +const args = parseArgs(process.argv.slice(2)); + +app.whenReady().then(async () => { + const profiles = require('../src/main/profiles'); + const transport = require('../src/main/transport'); + + const useTunnel = Boolean(args['ssh-host']); + const profile = profiles.upsert({ + name: 'smoke', + transport: useTunnel ? 'tunnel' : 'direct', + url: args.url || '', + tls: { mode: args.tls || 'system', fingerprint: args.fingerprint || '', caPath: args.ca || '' }, + ssh: { + host: args['ssh-host'] || '', + port: args['ssh-port'] || 22, + username: args['ssh-user'] || 'ubuntu', + authMethod: args.key ? 'key' : args.password ? 'password' : 'agent', + privateKeyPath: args.key ? expand(args.key) : '', + passphrase: args.passphrase, + password: args.password, + remoteHost: args['remote-host'] || '127.0.0.1', + remotePort: args['remote-port'] || 80, + remoteScheme: args['remote-scheme'] || 'http', + localPort: 0, + }, + }); + + transport.on('status', (event) => { + if (event.detail) log(` ${event.detail}`); + }); + + log(`transport : ${profile.transport}`); + log(useTunnel ? `ssh : ${profile.ssh.username}@${profile.ssh.host}` : `url : ${profile.url}`); + + const result = await transport.test(profile.id); + + if (result.ok) { + log(''); + log(' OK'); + log(` origin : ${result.origin}`); + log(` latency : ${result.latencyMs} ms`); + if (result.certificateFingerprint) log(` cert : ${result.certificateFingerprint}`); + if (result.hostKeyFingerprint) log(` host key : ${result.hostKeyFingerprint}`); + } else { + log(''); + log(` FAILED (${result.code})`); + log(` ${result.detail}`); + } + + fs.rmSync(tmpUserData, { recursive: true, force: true }); + app.exit(result.ok ? 0 : 1); +}); + +function parseArgs(argv) { + const out = {}; + for (let i = 0; i < argv.length; i += 1) { + if (!argv[i].startsWith('--')) continue; + const key = argv[i].slice(2); + const next = argv[i + 1]; + if (next === undefined || next.startsWith('--')) { + out[key] = true; + } else { + out[key] = next; + i += 1; + } + } + return out; +} + +function expand(p) { + return p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p; +} + +function log(line) { + process.stdout.write(`${line}\n`); +} diff --git a/desktop/scripts/tunnel-selftest.js b/desktop/scripts/tunnel-selftest.js new file mode 100644 index 0000000..0ea917e --- /dev/null +++ b/desktop/scripts/tunnel-selftest.js @@ -0,0 +1,303 @@ +'use strict'; + +/** + * End-to-end self-test for the SSH tunnel transport. + * + * Stands up a throwaway SSH server and a fake DeepSQL health endpoint on + * loopback, then drives the real SshTunnel + probe code against them. It + * exercises the parts that are otherwise only reachable with a live VM and a + * private key: key parsing, publickey auth, host-key pinning, direct-tcpip + * forwarding, and the sticky local port. + * + * npm run selftest:tunnel + */ + +const crypto = require('node:crypto'); +const fs = require('node:fs'); +const http = require('node:http'); +const net = require('node:net'); +const os = require('node:os'); +const path = require('node:path'); +const { app } = require('electron'); + +const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'deepsql-tunnel-test-')); +app.setPath('userData', tmpDir); + +const results = []; +function check(name, condition, detail = '') { + results.push({ name, ok: Boolean(condition), detail }); + process.stdout.write(` ${condition ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}\n`); +} + +app.whenReady().then(async () => { + const { Server } = require('ssh2'); + const { SshTunnel, hostKeyFingerprint } = require('../src/main/tunnel'); + const { probe } = require('../src/main/probe'); + + // ── Fixtures ─────────────────────────────────────────────────────────── + const { privateKey, publicKeyPem } = generateRsaPem(); + const keyPath = path.join(tmpDir, 'test-key.pem'); + fs.writeFileSync(keyPath, privateKey, { mode: 0o600 }); + + // Stands in for the DeepSQL nginx inside the VM. + // + // It also emulates Spring's CORS check, because that is a real failure mode + // of the tunnel transport: the origin is http://127.0.0.1:, and + // a VM whose CORS_ALLOWED_ORIGINS lists only its hostname answers 403 with a + // plain-text body. Spring rejects on the presence of `Origin` alone — there + // is no same-origin exemption — so this mirrors that rule exactly. + let lastProbeOrigin = null; + let corsAllowedOrigins = null; // null = allow everything + const upstream = http.createServer((req, res) => { + if (req.url === '/api/actuator/health') { + const origin = req.headers.origin || null; + lastProbeOrigin = origin; + if (origin && corsAllowedOrigins && !corsAllowedOrigins.includes(origin)) { + res.writeHead(403, { 'Content-Type': 'text/plain' }); + res.end('Invalid CORS request'); + return; + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end('{"status":"UP"}'); + return; + } + res.writeHead(404).end(); + }); + await listen(upstream, 0, '127.0.0.1'); + const upstreamPort = upstream.address().port; + + let forwardedTo = null; + // Flipped on to emulate a VM with `AllowTcpForwarding no`: login succeeds, + // every direct-tcpip channel is refused. + let refuseForwarding = false; + const sshServer = new Server({ hostKeys: [privateKey] }, (client) => { + client.on('authentication', (ctx) => { + // Accepting any publickey is fine here: the point is to exercise our + // client, not to be a real authenticator. + if (ctx.method === 'publickey') ctx.accept(); + else ctx.reject(['publickey']); + }); + client.on('ready', () => { + client.on('tcpip', (accept, reject, info) => { + if (refuseForwarding) { + // 1 = ADMINISTRATIVELY_PROHIBITED. Verified against a real OpenSSH + // server with `AllowTcpForwarding no`, which sends exactly this with + // the description "open failed". A bare reject() would default to + // CONNECT_FAILED and test the wrong branch. + reject(1, 'open failed'); + return; + } + forwardedTo = `${info.destIP}:${info.destPort}`; + const channel = accept(); + const socket = net.connect(info.destPort, info.destIP, () => { + channel.pipe(socket).pipe(channel); + }); + socket.on('error', () => channel.end()); + }); + }); + client.on('error', () => {}); + }); + await listen(sshServer, 0, '127.0.0.1'); + const sshPort = sshServer.address().port; + + const expectedHostKey = hostKeyFingerprint(publicKeyPem.sshWireFormat); + + // ── 1. First connection pins the host key and forwards traffic ───────── + const profile = makeProfile({ keyPath, sshPort, upstreamPort }); + const tunnel = new SshTunnel(profile, {}); + const { localPort, hostKeyFingerprint: seenHostKey } = await tunnel.start(); + + check('tunnel binds a loopback port', localPort > 0, `port ${localPort}`); + check( + 'host key fingerprint matches the server key', + seenHostKey === expectedHostKey, + seenHostKey, + ); + + const health = await probe(`http://127.0.0.1:${localPort}`, profile); + check('health probe succeeds through the tunnel', health.ok, health.detail); + check( + 'traffic is forwarded to the configured remote endpoint', + forwardedTo === `127.0.0.1:${upstreamPort}`, + forwardedTo, + ); + + // The probe must announce the origin the browser is about to use, or a CORS + // allowlist that omits it stays invisible until the user's first login POST. + check( + 'probe sends the browser origin so CORS is checked at connect time', + lastProbeOrigin === `http://127.0.0.1:${localPort}`, + lastProbeOrigin === null ? 'no Origin header sent' : lastProbeOrigin, + ); + + // Same tunnel, same healthy backend — only the allowlist changes. + corsAllowedOrigins = ['https://deepsql.example.com']; + const corsHealth = await probe(`http://127.0.0.1:${localPort}`, profile); + check( + 'an origin missing from CORS_ALLOWED_ORIGINS fails the probe', + !corsHealth.ok && corsHealth.status === 403, + `ok=${corsHealth.ok} status=${corsHealth.status}`, + ); + corsAllowedOrigins = null; + + // The listener must never be reachable from anything but loopback. + check( + 'listener is bound to loopback only', + tunnel.server.address().address === '127.0.0.1', + tunnel.server.address().address, + ); + + await tunnel.stop(); + check('stop() releases the local port', await portIsFree(localPort)); + + // ── 2. A changed host key is refused ─────────────────────────────────── + const tamperedProfile = makeProfile({ keyPath, sshPort, upstreamPort }); + tamperedProfile.ssh.hostKeyFingerprint = 'SHA256:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'; + const strictTunnel = new SshTunnel(tamperedProfile, {}); + let rejected = null; + try { + await strictTunnel.start(); + } catch (err) { + rejected = err; + } + await strictTunnel.stop(); + check( + 'mismatched host key aborts the connection', + rejected !== null, + rejected ? rejected.code : 'connection was allowed', + ); + + // ── 3. A refused forwarding channel fails before the listener binds ──── + // Regression guard: this used to surface as "socket hang up" only once the + // browser made a request, with nothing pointing at the SSH layer at all. + // + // ssh2's server-side reject() takes no arguments and always answers + // CONNECT_FAILED, so the live half of this test can only cover that branch. + // The ADMINISTRATIVELY_PROHIBITED branch — what a real `AllowTcpForwarding no` + // server sends — is checked directly below against the error shape observed + // from OpenSSH: `{ reason: 1, message: '(SSH) Channel open failure: open failed' }`. + refuseForwarding = true; + const deniedProfile = makeProfile({ keyPath, sshPort, upstreamPort }); + const deniedTunnel = new SshTunnel(deniedProfile, {}); + let denied = null; + try { + await deniedTunnel.start(); + } catch (err) { + denied = err; + } + await deniedTunnel.stop(); + check( + 'a refused channel fails start() instead of the later HTTP request', + denied?.code === 'remote-port-closed', + denied ? denied.code : 'start() succeeded', + ); + check( + 'the failure never leaves a listener bound', + deniedTunnel.server === null || deniedTunnel.localPort === null, + `server=${deniedTunnel.server === null ? 'null' : 'bound'}`, + ); + refuseForwarding = false; + + const classifier = new SshTunnel(makeProfile({ keyPath, sshPort, upstreamPort }), {}); + const prohibited = classifier.translateChannelError({ + reason: 1, + message: '(SSH) Channel open failure: open failed', + }); + check( + 'AllowTcpForwarding=no is named as the cause', + prohibited.code === 'forwarding-denied' && /allowtcpforwarding/i.test(prohibited.message), + prohibited.code, + ); + check( + 'a closed remote port is distinguished from a forwarding ban', + classifier.translateChannelError({ reason: 2 }).code === 'remote-port-closed', + ); + + // ── 4. Missing key file fails with an actionable message ─────────────── + const noKeyProfile = makeProfile({ keyPath: path.join(tmpDir, 'nope.pem'), sshPort, upstreamPort }); + let keyError = null; + try { + await new SshTunnel(noKeyProfile, {}).start(); + } catch (err) { + keyError = err; + } + check('missing key file is reported clearly', keyError?.code === 'key-unreadable', keyError?.code); + + // ── 5. Teardown ──────────────────────────────────────────────────────── + sshServer.close(); + upstream.close(); + fs.rmSync(tmpDir, { recursive: true, force: true }); + + const failed = results.filter((r) => !r.ok).length; + process.stdout.write(`\n${results.length - failed}/${results.length} checks passed\n`); + app.exit(failed === 0 ? 0 : 1); +}); + +function makeProfile({ keyPath, sshPort, upstreamPort }) { + return { + id: 'selftest', + name: 'selftest', + transport: 'tunnel', + tls: { mode: 'system' }, + ssh: { + host: '127.0.0.1', + port: sshPort, + username: 'tester', + authMethod: 'key', + privateKeyPath: keyPath, + remoteHost: '127.0.0.1', + remotePort: upstreamPort, + remoteScheme: 'http', + localPort: 0, + stickyLocalPort: 0, + hostKeyFingerprint: '', + }, + }; +} + +function generateRsaPem() { + const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', { + modulusLength: 2048, + privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, + publicKeyEncoding: { type: 'pkcs1', format: 'der' }, + }); + // ssh2 fingerprints the SSH wire format of the public key, so rebuild it from + // the RSA modulus/exponent rather than trusting the DER encoding. + const jwk = crypto.createPublicKey(privateKey).export({ format: 'jwk' }); + const e = Buffer.from(jwk.e, 'base64url'); + const n = Buffer.from(jwk.n, 'base64url'); + const sshWireFormat = Buffer.concat([ + sshString(Buffer.from('ssh-rsa')), + sshString(prefixZeroIfSigned(e)), + sshString(prefixZeroIfSigned(n)), + ]); + return { privateKey, publicKeyPem: { der: publicKey, sshWireFormat } }; +} + +function sshString(buffer) { + const length = Buffer.alloc(4); + length.writeUInt32BE(buffer.length); + return Buffer.concat([length, buffer]); +} + +function prefixZeroIfSigned(buffer) { + return buffer[0] & 0x80 ? Buffer.concat([Buffer.from([0]), buffer]) : buffer; +} + +function listen(server, port, host) { + return new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(port, host, () => resolve()); + }); +} + +function portIsFree(port) { + return new Promise((resolve) => { + const socket = net.connect(port, '127.0.0.1'); + socket.on('connect', () => { + socket.destroy(); + resolve(false); + }); + socket.on('error', () => resolve(true)); + }); +} diff --git a/desktop/src/main/config.js b/desktop/src/main/config.js new file mode 100644 index 0000000..a347321 --- /dev/null +++ b/desktop/src/main/config.js @@ -0,0 +1,55 @@ +'use strict'; + +/** + * Shared constants for the DeepSQL desktop client. + * + * The client is a *thin* client: it never bundles a copy of the DeepSQL web UI. + * It navigates a WebContentsView at the real DeepSQL origin (reached either + * directly over TLS or through an SSH tunnel), so the UI a user sees is always + * exactly the version their VM is running. There is no bundle/backend skew to + * manage, and cookies behave the same as they do in a browser because the app + * content is served from a single origin (the frontend nginx in + * docker/nginx/default.conf already fronts /api and /agent-api). + * + * CORS is the one thing that does *not* come for free: over a tunnel that single + * origin is http://127.0.0.1:, which the VM's CORS_ALLOWED_ORIGINS + * has to allow. See the note in probe.js — the probe sends an Origin header so a + * missing entry is caught here rather than at the user's first login attempt. + */ + +const path = require('node:path'); +const { app } = require('electron'); + +const IS_DEV = + process.env.DEEPSQL_DESKTOP_DEV === '1' || !app.isPackaged; + +// Health probe served by the Spring backend through the frontend nginx. +const HEALTH_PATH = '/api/actuator/health'; + +// How often the workspace re-probes the connection while a window is open. +const HEALTH_INTERVAL_MS = 20_000; + +// Height of the native top chrome above the embedded DeepSQL UI. +const CHROME_HEIGHT = 44; + +const RENDERER_DIR = path.join(__dirname, '..', 'renderer'); +const PRELOAD_DIR = path.join(__dirname, '..', 'preload'); + +const LAUNCHER_HTML = path.join(RENDERER_DIR, 'launcher', 'index.html'); +const CHROME_HTML = path.join(RENDERER_DIR, 'chrome', 'index.html'); +const LAUNCHER_PRELOAD = path.join(PRELOAD_DIR, 'launcher.js'); +const CHROME_PRELOAD = path.join(PRELOAD_DIR, 'chrome.js'); + +const PROTOCOL = 'deepsql'; + +module.exports = { + IS_DEV, + HEALTH_PATH, + HEALTH_INTERVAL_MS, + CHROME_HEIGHT, + LAUNCHER_HTML, + CHROME_HTML, + LAUNCHER_PRELOAD, + CHROME_PRELOAD, + PROTOCOL, +}; diff --git a/desktop/src/main/index.js b/desktop/src/main/index.js new file mode 100644 index 0000000..6d9cd07 --- /dev/null +++ b/desktop/src/main/index.js @@ -0,0 +1,139 @@ +'use strict'; + +/** + * DeepSQL desktop client — main process entry point. + * + * Lifecycle: launcher (pick a VM) → transport comes up (TLS or SSH tunnel) → + * workspace window opens on the resulting origin. Quitting tears every tunnel + * down; a leaked loopback listener would be a live hole into the VM. + */ + +const { app, BrowserWindow, shell } = require('electron'); + +const { PROTOCOL, IS_DEV } = require('./config'); +const ipc = require('./ipc'); +const menu = require('./menu'); +const updater = require('./updater'); +const launcher = require('./windows/launcher'); +const workspaces = require('./windows/workspace'); +const transport = require('./transport'); +const log = require('./logger'); + +// Two instances would fight over the same profiles.json and could bind the same +// sticky tunnel port; the second one just focuses the first. +if (!app.requestSingleInstanceLock()) { + app.quit(); +} else { + app.on('second-instance', (_event, argv) => { + const existing = launcher.get() || workspaces.all()[0]?.window; + if (existing) { + if (existing.isMinimized?.()) existing.restore(); + existing.focus(); + } else { + launcher.create(); + } + handleDeepLink(argv.find((arg) => arg.startsWith(`${PROTOCOL}://`))); + }); + + bootstrap(); +} + +function bootstrap() { + app.setName('DeepSQL'); + + if (process.defaultApp) { + // Dev: register the scheme with the electron binary + script path. + if (process.argv.length >= 2) { + app.setAsDefaultProtocolClient(PROTOCOL, process.execPath, [process.argv[1]]); + } + } else { + app.setAsDefaultProtocolClient(PROTOCOL); + } + + app.on('open-url', (event, url) => { + event.preventDefault(); + handleDeepLink(url); + }); + + app.whenReady().then(() => { + log.info('app', 'starting', { + version: app.getVersion(), + electron: process.versions.electron, + platform: `${process.platform}-${process.arch}`, + dev: IS_DEV, + }); + + ipc.register(); + menu.build(); + launcher.create(); + updater.init(); + handleDeepLinkFromArgv(); + + app.on('activate', () => { + if (BrowserWindow.getAllWindows().length === 0 && workspaces.all().length === 0) { + launcher.create(); + } + }); + }); + + app.on('window-all-closed', async () => { + await transport.disconnectAll(); + if (process.platform !== 'darwin') app.quit(); + }); + + app.on('before-quit', async (event) => { + if (transport.active.size === 0) return; + event.preventDefault(); + await transport.disconnectAll(); + app.quit(); + }); + + // Defence in depth: even if a view slipped past its own navigation guard, + // nothing may open a Node-enabled window or reach a non-http scheme. + app.on('web-contents-created', (_event, contents) => { + contents.on('will-attach-webview', (event) => event.preventDefault()); + contents.setWindowOpenHandler(({ url }) => { + if (/^https?:/i.test(url)) shell.openExternal(url); + return { action: 'deny' }; + }); + }); +} + +/** + * `deepsql://connect?url=https://vm.example.com&name=Prod` opens the launcher + * with a new profile pre-filled — the one-click "connect to our VM" link an + * admin can paste into onboarding docs. + */ +function handleDeepLink(rawUrl) { + if (!rawUrl) return; + let parsed; + try { + parsed = new URL(rawUrl); + } catch { + return; + } + if (parsed.protocol !== `${PROTOCOL}:`) return; + + const window = launcher.create(); + const payload = { + action: parsed.hostname || 'connect', + url: parsed.searchParams.get('url') || '', + name: parsed.searchParams.get('name') || '', + transport: parsed.searchParams.get('transport') === 'tunnel' ? 'tunnel' : 'direct', + sshHost: parsed.searchParams.get('sshHost') || '', + sshUser: parsed.searchParams.get('sshUser') || '', + }; + const send = () => window.webContents.send('deeplink', payload); + if (window.webContents.isLoading()) { + window.webContents.once('did-finish-load', send); + } else { + send(); + } + log.info('app', 'handled deep link', payload); +} + +/** Windows/Linux cold start with a protocol argument. */ +function handleDeepLinkFromArgv() { + const arg = process.argv.find((value) => value.startsWith(`${PROTOCOL}://`)); + if (arg) handleDeepLink(arg); +} diff --git a/desktop/src/main/ipc.js b/desktop/src/main/ipc.js new file mode 100644 index 0000000..610a42c --- /dev/null +++ b/desktop/src/main/ipc.js @@ -0,0 +1,177 @@ +'use strict'; + +/** Every renderer→main entry point in the app lives here. */ + +const { ipcMain, dialog, shell, app } = require('electron'); + +const profilesStore = require('./profiles'); +const secrets = require('./secrets'); +const transport = require('./transport'); +const launcher = require('./windows/launcher'); +const workspaces = require('./windows/workspace'); +const log = require('./logger'); + +function register() { + // ── App ──────────────────────────────────────────────────────────────── + ipcMain.handle('app:info', () => ({ + // Not app.getVersion(): under `electron + + diff --git a/desktop/src/renderer/launcher/index.html b/desktop/src/renderer/launcher/index.html new file mode 100644 index 0000000..3b7b923 --- /dev/null +++ b/desktop/src/renderer/launcher/index.html @@ -0,0 +1,252 @@ + + + + + + DeepSQL + + + + +
+ + + + +
+
+ +
+ +

Connect to your DeepSQL server

+

+ DeepSQL runs on your own VM or hardware. Point this client at it over TLS, or + tunnel in over SSH when the ports are closed to the internet. +

+ +
+ + +
+
+ + + + diff --git a/desktop/src/renderer/launcher/launcher.css b/desktop/src/renderer/launcher/launcher.css new file mode 100644 index 0000000..ede7aaa --- /dev/null +++ b/desktop/src/renderer/launcher/launcher.css @@ -0,0 +1,532 @@ +/* Launcher — the connection manager. Mirrors AppSidebar.module.css proportions + from the web app (220px rail, 1px #e5e7eb divider, 8px nav radii). */ + +.shell { + display: flex; + height: 100vh; + overflow: hidden; +} + +/* Draggable strip so a frameless/hidden-inset window can still be moved. */ +.drag-strip { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 38px; + -webkit-app-region: drag; + pointer-events: auto; +} + +.drag-strip * { + -webkit-app-region: no-drag; +} + +/* ── Sidebar ──────────────────────────────────────────────────────────── */ +.sidebar { + position: relative; + width: 248px; + min-width: 248px; + display: flex; + flex-direction: column; + background: var(--color-white); + border-right: 1px solid var(--color-border); +} + +.brand { + display: flex; + align-items: center; + gap: 10px; + padding: 46px 16px 12px; + flex-shrink: 0; +} + +.brand-mark { + width: 28px; + height: 28px; + background: var(--color-ink); + border-radius: 7px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.brand-text { + font-size: 15px; + font-weight: 600; + color: var(--color-ink); + letter-spacing: -0.01em; +} + +.rail-label { + padding: 10px 18px 6px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--color-light-5); +} + +.profile-list { + flex: 1; + overflow-y: auto; + padding: 0 8px 8px; + display: flex; + flex-direction: column; + gap: 1px; +} + +.profile-item { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 9px 10px; + border-radius: 8px; + text-align: left; + color: var(--color-light-6); + transition: background 0.1s, color 0.1s; +} + +.profile-item:hover { + background: var(--color-light-2); + color: var(--color-text); +} + +.profile-item.is-active { + background: var(--color-light-2); + color: var(--color-text); + font-weight: 500; +} + +.profile-dot { + width: 7px; + height: 7px; + border-radius: 999px; + background: var(--color-light-4); + flex-shrink: 0; +} + +.profile-dot.is-live { + background: var(--color-success); + box-shadow: 0 0 0 3px var(--color-success-soft); +} + +.profile-copy { + min-width: 0; + display: flex; + flex-direction: column; + line-height: 1.3; +} + +.profile-name { + font-size: 13.5px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.profile-sub { + font-size: 11.5px; + color: var(--color-light-5); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.sidebar-footer { + border-top: 1px solid var(--color-border); + padding: 10px 12px 12px; + flex-shrink: 0; +} + +.new-connection { + width: 100%; + justify-content: flex-start; + gap: 8px; + padding: 8px 10px; + color: var(--color-light-8); + font-size: 13.5px; + font-weight: 500; +} + +.app-meta { + margin-top: 8px; + padding: 0 4px; + font-size: 11px; + line-height: 1.5; + color: var(--color-light-5); +} + +.app-meta button { + padding: 0; + font-size: 11px; + color: var(--color-light-6); + text-decoration: underline; + background: none; +} + +.app-meta button:hover { + background: none; + color: var(--color-text); +} + +/* ── Detail pane ──────────────────────────────────────────────────────── */ +.pane { + position: relative; + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.pane-drag { + height: 30px; +} + +.empty-state { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + gap: 14px; + padding: 0 64px; +} + +.empty-mark { + width: 56px; + height: 56px; + border-radius: var(--radius-lg); + background: var(--color-white); + border: 1px solid var(--color-border); + box-shadow: var(--shadow-card); + display: flex; + align-items: center; + justify-content: center; +} + +.empty-state h1 { + margin: 0; + font-size: 20px; + font-weight: 600; + letter-spacing: -0.02em; +} + +.empty-state p { + margin: 0; + max-width: 440px; + color: var(--color-text-secondary); +} + +.empty-state .btn-primary { + margin-top: 8px; +} + +/* ── Editor ───────────────────────────────────────────────────────────── */ +.editor { + flex: 1; + min-height: 0; + display: flex; + flex-direction: column; +} + +.editor-head { + padding: 34px 32px 14px; + flex-shrink: 0; +} + +.editor-title-row { + display: flex; + align-items: center; + gap: 12px; +} + +.title-input { + flex: 1; + min-width: 0; + font-size: 21px; + font-weight: 600; + letter-spacing: -0.02em; + padding: 4px 8px; + margin-left: -8px; + border: 1px solid transparent; + background: transparent; + border-radius: var(--radius-sm); +} + +.title-input:hover { + border-color: var(--color-light-3); +} + +.title-input:focus { + background: var(--color-white); + border-color: var(--color-light-4); + box-shadow: none; +} + +.editor-subtitle { + margin: 4px 0 0; + font-size: 12.5px; + color: var(--color-light-5); +} + +.pill { + flex-shrink: 0; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + border-radius: var(--radius-button); + font-size: 12px; + font-weight: 500; + white-space: nowrap; +} + +.pill-idle { + background: var(--color-light-2); + color: var(--color-light-6); +} + +.pill-busy { + background: var(--color-warning-soft); + color: #92400e; +} + +.pill-live { + background: var(--color-success-soft); + color: #15803d; +} + +.pill-error { + background: var(--color-danger-soft); + color: var(--color-danger-text); +} + +.editor-body { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 6px 32px 24px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.card { + background: var(--color-white); + border: 1px solid var(--color-border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-card); + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.card-head h2 { + margin: 0; + font-size: 13px; + font-weight: 600; + letter-spacing: 0.02em; + text-transform: uppercase; + color: var(--color-light-6); +} + +.card-head-inner { + margin-top: 4px; + padding-top: 16px; + border-top: 1px solid var(--color-border); +} + +.segmented { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.seg { + flex-direction: column; + align-items: flex-start; + gap: 3px; + padding: 13px 15px; + border: 1px solid var(--color-light-3); + border-radius: var(--radius-md); + background: var(--color-white); + text-align: left; + transition: border-color 0.12s, background 0.12s; +} + +.seg strong { + font-size: 13.5px; + font-weight: 600; + color: var(--color-text); +} + +.seg span { + font-size: 12px; + font-weight: 400; + color: var(--color-light-5); + line-height: 1.45; +} + +.seg:hover { + background: var(--color-white); + border-color: var(--color-light-4); +} + +.seg.is-selected { + border-color: var(--color-ink); + background: #fafafa; +} + +.field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.field-label { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + color: var(--color-light-5); +} + +.field-label em { + font-style: normal; + font-weight: 400; + text-transform: none; + letter-spacing: 0; + color: var(--color-light-4); +} + +.field-hint { + font-size: 12px; + line-height: 1.5; + color: var(--color-light-5); +} + +.field-hint code, +.notice code, +code.mono { + font-family: var(--font-family-mono); + font-size: 11.5px; + background: var(--color-light-2); + padding: 1px 5px; + border-radius: 4px; + user-select: text; +} + +.grid-3 { + display: grid; + grid-template-columns: 2fr 1fr 1fr; + gap: 12px; +} + +.file-row { + display: flex; + gap: 8px; +} + +.file-row input { + flex: 1; + min-width: 0; +} + +.notice { + font-size: 12.5px; + line-height: 1.55; + padding: 11px 14px; + border-radius: var(--radius-md); +} + +.notice-danger { + background: var(--color-danger-soft); + border: 1px solid var(--color-danger-border); + color: var(--color-danger-text); +} + +.notice-muted { + background: var(--color-light-2); + color: var(--color-light-6); +} + +.host-key { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 11px 14px; + background: var(--color-light-2); + border-radius: var(--radius-md); +} + +.host-key code { + display: block; + margin-top: 2px; + background: transparent; + padding: 0; + color: var(--color-light-8); + word-break: break-all; +} + +/* ── Footer ───────────────────────────────────────────────────────────── */ +.editor-foot { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 16px; + padding: 14px 32px; + background: var(--color-white); + border-top: 1px solid var(--color-border); +} + +.foot-left { + flex-shrink: 0; +} + +.foot-right { + display: flex; + gap: 10px; + flex-shrink: 0; +} + +.feedback { + flex: 1; + min-width: 0; + font-size: 12.5px; + line-height: 1.45; + color: var(--color-light-6); + user-select: text; +} + +.feedback.is-error { + color: var(--color-danger-text); +} + +.feedback.is-ok { + color: var(--color-success); +} + +.spinner { + display: inline-block; + width: 11px; + height: 11px; + margin-right: 7px; + vertical-align: -1px; + border: 1.5px solid currentColor; + border-top-color: transparent; + border-radius: 999px; + animation: spin 0.7s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} diff --git a/desktop/src/renderer/launcher/launcher.js b/desktop/src/renderer/launcher/launcher.js new file mode 100644 index 0000000..39167dc --- /dev/null +++ b/desktop/src/renderer/launcher/launcher.js @@ -0,0 +1,509 @@ +'use strict'; + +/* Launcher renderer. No framework on purpose: this is three screens of chrome + around an IPC surface, and a build step here would be a second toolchain to + keep alive for no gain. The DeepSQL app itself is React — it is loaded from + the server, not reimplemented. */ + +const api = window.deepsql; + +const el = (id) => document.getElementById(id); + +const dom = { + list: el('profile-list'), + meta: el('app-meta'), + empty: el('empty-state'), + editor: el('editor'), + subtitle: el('editor-subtitle'), + statusPill: el('status-pill'), + feedback: el('feedback'), + hostKeyRow: el('host-key-row'), + hostKeyValue: el('host-key-value'), + secretHint: el('secret-hint'), + fields: { + name: el('f-name'), + url: el('f-url'), + tlsMode: el('f-tls-mode'), + tlsFingerprint: el('f-tls-fingerprint'), + tlsCa: el('f-tls-ca'), + sshHost: el('f-ssh-host'), + sshPort: el('f-ssh-port'), + sshUser: el('f-ssh-user'), + sshAuth: el('f-ssh-auth'), + sshKey: el('f-ssh-key'), + sshPassphrase: el('f-ssh-passphrase'), + sshPassword: el('f-ssh-password'), + remoteHost: el('f-remote-host'), + remotePort: el('f-remote-port'), + localPort: el('f-local-port'), + }, +}; + +const state = { + profiles: [], + selectedId: null, + /** Unsaved new profile, held here until the first save. */ + draft: null, + transport: 'direct', + active: new Set(), + busy: false, + info: null, +}; + +// ── Rendering ──────────────────────────────────────────────────────────── + +function selected() { + if (state.draft) return state.draft; + return state.profiles.find((p) => p.id === state.selectedId) || null; +} + +function renderList() { + dom.list.replaceChildren(); + + for (const profile of state.profiles) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'profile-item'; + if (!state.draft && profile.id === state.selectedId) item.classList.add('is-active'); + + const dot = document.createElement('span'); + dot.className = `profile-dot${state.active.has(profile.id) ? ' is-live' : ''}`; + + const copy = document.createElement('span'); + copy.className = 'profile-copy'; + const name = document.createElement('span'); + name.className = 'profile-name'; + name.textContent = profile.name; + const sub = document.createElement('span'); + sub.className = 'profile-sub'; + sub.textContent = describe(profile); + copy.append(name, sub); + + item.append(dot, copy); + item.addEventListener('click', () => select(profile.id)); + dom.list.append(item); + } + + if (state.draft) { + const item = document.createElement('button'); + item.type = 'button'; + item.className = 'profile-item is-active'; + const dot = document.createElement('span'); + dot.className = 'profile-dot'; + const copy = document.createElement('span'); + copy.className = 'profile-copy'; + const name = document.createElement('span'); + name.className = 'profile-name'; + name.textContent = state.draft.name || 'New connection'; + const sub = document.createElement('span'); + sub.className = 'profile-sub'; + sub.textContent = 'Unsaved'; + copy.append(name, sub); + item.append(dot, copy); + dom.list.append(item); + } +} + +function describe(profile) { + if (profile.transport === 'tunnel') { + const { username, host, remotePort } = profile.ssh; + return host ? `ssh ${username}@${host} → :${remotePort}` : 'SSH tunnel'; + } + try { + return new URL(profile.url).host; + } catch { + return 'No URL set'; + } +} + +function renderEditor() { + const profile = selected(); + const hasProfile = Boolean(profile); + dom.empty.hidden = hasProfile; + dom.editor.hidden = !hasProfile; + if (!profile) return; + + state.transport = profile.transport; + + dom.fields.name.value = profile.name || ''; + dom.fields.url.value = profile.url || ''; + dom.fields.tlsMode.value = profile.tls?.mode || 'system'; + dom.fields.tlsFingerprint.value = profile.tls?.fingerprint || ''; + dom.fields.tlsCa.value = profile.tls?.caPath || ''; + + const ssh = profile.ssh || {}; + dom.fields.sshHost.value = ssh.host || ''; + dom.fields.sshPort.value = ssh.port ?? 22; + dom.fields.sshUser.value = ssh.username || ''; + dom.fields.sshAuth.value = ssh.authMethod || 'key'; + dom.fields.sshKey.value = ssh.privateKeyPath || ''; + dom.fields.sshPassphrase.value = ''; + dom.fields.sshPassword.value = ''; + dom.fields.remoteHost.value = ssh.remoteHost || '127.0.0.1'; + dom.fields.remotePort.value = ssh.remotePort ?? 3000; + dom.fields.localPort.value = ssh.localPort ?? 0; + + dom.hostKeyRow.hidden = !ssh.hostKeyFingerprint; + dom.hostKeyValue.textContent = ssh.hostKeyFingerprint || ''; + + dom.secretHint.textContent = ssh.hasStoredPassphrase + ? state.info?.keychainAvailable + ? 'A passphrase is saved in your OS keychain. Type here only to replace it.' + : 'A passphrase is held for this session only — the OS keychain is unavailable.' + : ''; + + dom.subtitle.textContent = profile.lastConnectedAt + ? `Last connected ${formatWhen(profile.lastConnectedAt)}` + : state.draft + ? 'Not saved yet' + : 'Never connected'; + + el('delete-profile').hidden = Boolean(state.draft); + + syncVisibility(); + setStatus( + state.active.has(profile.id) ? 'live' : 'idle', + state.active.has(profile.id) ? 'Connected' : 'Not connected', + ); +} + +/** Show only the fields the current transport / TLS mode / auth method needs. */ +function syncVisibility() { + const transport = state.transport; + for (const seg of document.querySelectorAll('.seg')) { + seg.classList.toggle('is-selected', seg.dataset.transport === transport); + } + for (const panel of document.querySelectorAll('[data-panel]')) { + panel.hidden = panel.dataset.panel !== transport; + } + const tlsMode = dom.fields.tlsMode.value; + for (const node of document.querySelectorAll('[data-tls]')) { + node.hidden = node.dataset.tls !== tlsMode; + } + const auth = dom.fields.sshAuth.value; + for (const node of document.querySelectorAll('[data-auth]')) { + node.hidden = node.dataset.auth !== auth; + } +} + +function setStatus(kind, text) { + dom.statusPill.className = `pill pill-${kind}`; + dom.statusPill.textContent = text; +} + +function say(message, kind = '') { + dom.feedback.className = `feedback${kind ? ` is-${kind}` : ''}`; + dom.feedback.replaceChildren(); + if (kind === 'busy') { + const spinner = document.createElement('span'); + spinner.className = 'spinner'; + dom.feedback.append(spinner); + } + dom.feedback.append(document.createTextNode(message)); +} + +function formatWhen(iso) { + const then = new Date(iso); + const minutes = Math.round((Date.now() - then.getTime()) / 60000); + if (minutes < 1) return 'just now'; + if (minutes < 60) return `${minutes}m ago`; + if (minutes < 60 * 24) return `${Math.round(minutes / 60)}h ago`; + return then.toLocaleDateString(); +} + +function setBusy(busy) { + state.busy = busy; + el('connect').disabled = busy; + el('test-connection').disabled = busy; +} + +// ── Form → payload ─────────────────────────────────────────────────────── + +function readForm() { + const profile = selected(); + const payload = { + id: state.draft ? undefined : profile?.id, + name: dom.fields.name.value, + transport: state.transport, + url: dom.fields.url.value, + tls: { + mode: dom.fields.tlsMode.value, + fingerprint: dom.fields.tlsFingerprint.value, + caPath: dom.fields.tlsCa.value, + }, + ssh: { + host: dom.fields.sshHost.value, + port: dom.fields.sshPort.value, + username: dom.fields.sshUser.value, + authMethod: dom.fields.sshAuth.value, + privateKeyPath: dom.fields.sshKey.value, + remoteHost: dom.fields.remoteHost.value, + remotePort: dom.fields.remotePort.value, + localPort: dom.fields.localPort.value, + }, + }; + + // An empty secret box means "leave what is stored alone", not "clear it" — + // otherwise every save would wipe the keychain entry the user just made. + if (dom.fields.sshPassphrase.value) payload.ssh.passphrase = dom.fields.sshPassphrase.value; + if (dom.fields.sshPassword.value) payload.ssh.password = dom.fields.sshPassword.value; + + return payload; +} + +/** Persist the form and return the saved profile. */ +async function save() { + const saved = await api.profiles.save(readForm()); + state.draft = null; + state.selectedId = saved.id; + state.profiles = await api.profiles.list(); + renderList(); + return saved; +} + +function validate() { + if (state.transport === 'direct') { + if (!dom.fields.url.value.trim()) return 'Enter the DeepSQL server URL.'; + if (dom.fields.tlsMode.value === 'custom-ca' && !dom.fields.tlsCa.value.trim()) { + return 'Select the CA bundle to verify against.'; + } + return null; + } + if (!dom.fields.sshHost.value.trim()) return 'Enter the SSH host.'; + if (!dom.fields.sshUser.value.trim()) return 'Enter the SSH username.'; + if (dom.fields.sshAuth.value === 'key' && !dom.fields.sshKey.value.trim()) { + return 'Select the private key file to authenticate with.'; + } + return null; +} + +// ── Actions ────────────────────────────────────────────────────────────── + +function select(id) { + state.draft = null; + state.selectedId = id; + say(''); + renderList(); + renderEditor(); +} + +function newProfile(prefill = {}) { + state.draft = { + id: null, + name: prefill.name || 'DeepSQL VM', + transport: prefill.transport || 'direct', + url: prefill.url || '', + tls: { mode: 'system', fingerprint: '', caPath: '' }, + ssh: { + host: prefill.sshHost || '', + port: 22, + username: prefill.sshUser || 'ubuntu', + authMethod: 'key', + privateKeyPath: '', + remoteHost: '127.0.0.1', + remotePort: 3000, + localPort: 0, + hostKeyFingerprint: '', + hasStoredPassphrase: false, + hasStoredPassword: false, + }, + lastConnectedAt: null, + }; + state.selectedId = null; + say(''); + renderList(); + renderEditor(); + dom.fields.name.focus(); + dom.fields.name.select(); +} + +async function testConnection() { + const problem = validate(); + if (problem) { + say(problem, 'error'); + return; + } + setBusy(true); + setStatus('busy', 'Testing'); + say('Testing connection…', 'busy'); + try { + const profile = await save(); + const result = await api.profiles.test(profile.id, transientSecrets()); + if (result.ok) { + setStatus('idle', 'Not connected'); + say( + `DeepSQL responded in ${result.latencyMs} ms${ + result.hostKeyFingerprint ? ` · host key ${result.hostKeyFingerprint}` : '' + }.`, + 'ok', + ); + state.profiles = await api.profiles.list(); + renderEditor(); + } else { + setStatus('error', 'Failed'); + say(result.detail, 'error'); + } + } catch (err) { + setStatus('error', 'Failed'); + say(err.message, 'error'); + } finally { + setBusy(false); + } +} + +async function connect() { + const problem = validate(); + if (problem) { + say(problem, 'error'); + return; + } + setBusy(true); + setStatus('busy', 'Connecting'); + say('Connecting…', 'busy'); + try { + const profile = await save(); + const result = await api.connection.open(profile.id, transientSecrets()); + if (result.ok) { + state.active.add(profile.id); + setStatus('live', 'Connected'); + say('Connected. Opening DeepSQL…', 'ok'); + renderList(); + } else { + setStatus('error', 'Failed'); + say(result.detail, 'error'); + } + } catch (err) { + setStatus('error', 'Failed'); + say(err.message, 'error'); + } finally { + setBusy(false); + } +} + +/** + * Secrets typed right now are also passed straight to the transport, so a + * passphrase works on this attempt even when the OS keychain refused to store + * it (headless Linux) or the user is deliberately not saving it. + */ +function transientSecrets() { + return { + passphrase: dom.fields.sshPassphrase.value || undefined, + password: dom.fields.sshPassword.value || undefined, + }; +} + +async function deleteProfile() { + const profile = selected(); + if (!profile?.id) return; + await api.profiles.remove(profile.id); + state.active.delete(profile.id); + state.profiles = await api.profiles.list(); + state.selectedId = state.profiles[0]?.id || null; + state.draft = null; + renderList(); + renderEditor(); + say('Connection deleted.'); +} + +// ── Wiring ─────────────────────────────────────────────────────────────── + +function wire() { + el('new-connection').addEventListener('click', () => newProfile()); + el('empty-new').addEventListener('click', () => newProfile()); + el('delete-profile').addEventListener('click', deleteProfile); + el('test-connection').addEventListener('click', testConnection); + + dom.editor.addEventListener('submit', (event) => { + event.preventDefault(); + connect(); + }); + + for (const seg of document.querySelectorAll('.seg')) { + seg.addEventListener('click', () => { + state.transport = seg.dataset.transport; + syncVisibility(); + say(''); + }); + } + + dom.fields.tlsMode.addEventListener('change', syncVisibility); + dom.fields.sshAuth.addEventListener('change', syncVisibility); + + dom.fields.name.addEventListener('input', () => { + if (state.draft) state.draft.name = dom.fields.name.value; + renderList(); + }); + + el('browse-key').addEventListener('click', async () => { + const picked = await api.dialog.pickPrivateKey(); + if (picked) dom.fields.sshKey.value = picked; + }); + + el('browse-ca').addEventListener('click', async () => { + const picked = await api.dialog.pickCaBundle(); + if (picked) dom.fields.tlsCa.value = picked; + }); + + el('clear-host-key').addEventListener('click', async () => { + const profile = selected(); + if (!profile?.id) return; + await api.profiles.clearHostKey(profile.id); + state.profiles = await api.profiles.list(); + renderEditor(); + say('Pinned host key cleared. The next connection will pin whatever the VM presents.'); + }); + + api.onStatus((payload) => { + if (state.busy && payload.detail) say(payload.detail, 'busy'); + if (payload.status === 'ready') state.active.add(payload.profileId); + if (payload.status === 'closed') state.active.delete(payload.profileId); + renderList(); + }); + + api.onWorkspaceClosed(({ profileId }) => { + state.active.delete(profileId); + renderList(); + if (selected()?.id === profileId) setStatus('idle', 'Not connected'); + }); + + api.onDeepLink((payload) => { + newProfile(payload); + say('Pre-filled from a deepsql:// link. Review it, then connect.'); + }); +} + +async function renderMeta() { + const info = state.info; + dom.meta.replaceChildren(); + + const version = document.createElement('div'); + version.textContent = `DeepSQL Desktop ${info.version} · Electron ${info.electron}`; + + const keychain = document.createElement('div'); + keychain.textContent = info.keychainAvailable + ? 'Secrets stored in the OS keychain' + : 'No OS keychain — secrets kept for this session only'; + + const logs = document.createElement('button'); + logs.type = 'button'; + logs.textContent = 'Open log file'; + logs.addEventListener('click', () => api.app.openLog()); + + dom.meta.append(version, keychain, logs); +} + +async function boot() { + state.info = await api.app.info(); + state.profiles = await api.profiles.list(); + state.active = new Set(await api.connection.activeIds()); + state.selectedId = state.profiles[0]?.id || null; + + wire(); + await renderMeta(); + renderList(); + renderEditor(); + + if (state.profiles.length === 0) newProfile(); +} + +boot(); diff --git a/desktop/src/renderer/shared/theme.css b/desktop/src/renderer/shared/theme.css new file mode 100644 index 0000000..099a9d5 --- /dev/null +++ b/desktop/src/renderer/shared/theme.css @@ -0,0 +1,206 @@ +/* + * DeepSQL design tokens, lifted from src/index.css in the web app so the native + * shell and the embedded UI read as one product. Keep these in sync: if the web + * app's palette moves, this file moves with it. + * + * The webfont is loaded from Google Fonts exactly as the web app does. When the + * machine is offline the stack falls through to the system UI font, which is + * why the fallbacks are spelled out rather than left to `sans-serif`. + */ +@import url("https://fonts.googleapis.com/css2?family=Maven+Pro:wght@400;500;600;700;800&display=swap"); + +:root { + --font-family-sans: "Maven Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, sans-serif; + --font-family-mono: "JetBrains Mono", "SF Mono", "Fira Code", "IBM Plex Mono", Menlo, + Consolas, monospace; + + --color-white: #ffffff; + --color-canvas: #f5f5f7; + --color-light-2: #f3f4f6; + --color-light-3: #e5e7eb; + --color-light-4: #d1d5db; + --color-light-5: #9ca3af; + --color-light-6: #6b7280; + --color-light-8: #374151; + --color-text: #111827; + --color-text-secondary: #6b7280; + --color-ink: #0d0d0d; + --color-primary: #000000; + --color-primary-dark: #333333; + --color-border: #e5e7eb; + + --color-success: #16a34a; + --color-success-soft: #dcfce7; + --color-warning: #f59e0b; + --color-warning-soft: #fef3c7; + --color-danger: #ef4444; + --color-danger-dark: #dc2626; + --color-danger-soft: #fef2f2; + --color-danger-border: #fecaca; + --color-danger-text: #b91c1c; + + --radius-sm: 0.5rem; + --radius-md: 0.75rem; + --radius-lg: 1rem; + --radius-button: 9999px; + + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05); + --shadow-card: 0 4px 24px rgba(0, 0, 0, 0.04); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* The UA rule for [hidden] is `display: none`, which any later `display: flex` + on the same element silently beats. Both renderers toggle visibility with the + hidden attribute, so it has to win outright. */ +[hidden] { + display: none !important; +} + +html, +body { + height: 100%; +} + +body { + margin: 0; + background: var(--color-canvas); + color: var(--color-text); + font-family: var(--font-family-sans); + font-size: 14px; + line-height: 1.6; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + overflow: hidden; + user-select: none; + cursor: default; +} + +button { + font-family: inherit; + font-size: 14px; + line-height: 1.4; + border: none; + background: transparent; + color: var(--color-light-8); + border-radius: 8px; + padding: 6px 12px; + font-weight: 500; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + cursor: pointer; + letter-spacing: -0.01em; + transition: background 0.15s ease, color 0.15s ease, opacity 0.15s ease; +} + +button:hover:not(:disabled) { + background: var(--color-light-2); + color: var(--color-text); +} + +button:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +button:focus-visible, +input:focus-visible, +select:focus-visible { + outline: 2px solid var(--color-light-4); + outline-offset: 1px; +} + +input, +select { + font-family: inherit; + font-size: 14px; + width: 100%; + padding: 9px 12px; + color: var(--color-text); + background: var(--color-white); + border: 1px solid var(--color-light-4); + border-radius: var(--radius-md); + transition: border-color 0.15s ease, box-shadow 0.15s ease; + user-select: text; +} + +input::placeholder { + color: var(--color-light-5); +} + +input:focus, +select:focus { + outline: none; + border-color: var(--color-light-6); + box-shadow: 0 0 0 3px rgba(209, 213, 219, 0.45); +} + +.btn-primary { + background: var(--color-primary); + color: var(--color-white); + border-radius: var(--radius-button); + padding: 10px 22px; + font-weight: 600; +} + +.btn-primary:hover:not(:disabled) { + background: var(--color-primary-dark); + color: var(--color-white); +} + +.btn-secondary { + background: var(--color-white); + border: 1px solid var(--color-light-4); + border-radius: var(--radius-button); + padding: 9px 18px; + color: var(--color-light-8); + font-weight: 500; +} + +.btn-secondary:hover:not(:disabled) { + background: var(--color-light-2); +} + +.btn-danger { + color: var(--color-danger-dark); +} + +.btn-danger:hover:not(:disabled) { + background: var(--color-danger-soft); + color: var(--color-danger-text); +} + +.mono { + font-family: var(--font-family-mono); + font-size: 12px; + letter-spacing: -0.01em; +} + +::-webkit-scrollbar { + width: 10px; + height: 10px; +} + +::-webkit-scrollbar-thumb { + background: var(--color-light-4); + border: 3px solid transparent; + background-clip: content-box; + border-radius: 999px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--color-light-5); + background-clip: content-box; +} + +::-webkit-scrollbar-track { + background: transparent; +} diff --git a/docker-compose.yml b/docker-compose.yml index cb33e5d..01f36a9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -83,8 +83,12 @@ services: ENCRYPTION_KEYS: ${ENCRYPTION_KEYS:-} ENCRYPTION_KEY_ID: ${ENCRYPTION_KEY_ID:-} - # CORS — allow the frontend service (and optional custom domain) - cors.allowed.origins: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000} + # CORS — allow the frontend service (and optional custom domain). + # Keep the loopback patterns when you set CORS_ALLOWED_ORIGINS: this + # variable REPLACES the list, and the desktop client's SSH tunnel serves + # the app from http://127.0.0.1:. Dropping them makes every + # tunnel login fail with a bare 403 "Invalid CORS request". + cors.allowed.origins: ${CORS_ALLOWED_ORIGINS:-http://localhost:3000,http://127.0.0.1:*,http://localhost:*} # Dashboard generation + Slack/channels call Hermes server-side (AgentChatClient), # not via the browser /agent-api proxy. Point at the host webui; there is no