Migrate to Electrobun 2.0.2 (Hutch)

Electrobun 2.x replaces the vendored CLI with Hutch: the npm package is now a
bootstrap whose exports are a throwing stub, and the SDK is projected into
.hutch/devkit. The app stays on the Bun main process — `mainProcess: "bun"` is
load-bearing, since the 2.x default (Cottontail) ignores build.bun and with it
the ssh2 externals — and the bundle now ships the pinned Bun 1.4.0.

- electrobun 2.0.2 as an exact devDependency. `electrobun prepare` takes the
  old CLI re-sign step's slot in postinstall and every dev/build/start/release
  script: tsc (root tsconfig extends the devkit's) and the Angular build
  (electrobun/view mapped into the devkit) resolve the SDK there.
- Imports move to electrobun/main. The CLI re-sign script and the v1 "three"
  type shim are gone (Hutch's binaries are validly signed).
- UI: Electroview's bunSocket is hostSocket; noImplicitReturns is off because
  the devkit's raw-TS browser SDK compiles inside the UI program.
- Updater.applyUpdate() now consults before-quit, so the transfers/unsaved
  guard's "Quit Anyway" re-runs the apply instead of quitting without it.
- 2.x applies trafficLightOffset from macOS's default button position (1.18.1
  clamped it inside the titlebar); {12, 4} centres the lights in the header.
- Releases: mac-x64 dropped (2.x publishes no macOS x64 core, so no Rosetta
  path). artifacts/ is emptied before each build and published verbatim.
  Installers lost the stable- prefix (the update feed kept it), so release.ts,
  deploy.sh and the site links follow. Linux ships Electrobun's Setup
  installer instead of the portable tarball and its miniSCP.sh wrapper.
- Docs cover all of the above, plus re-checks against 2.0.2: still no native
  modal windows; window close is now cancellable through will-close.

Verified: tsc, build:ui, a stable mac-arm64 release (dmg + the v1-compatible
stable-macos-arm64 feed) and dev launches (RPC, Settings window, traffic
lights). Windows/Linux builds and the v1 -> 2.x Windows update bridge still
need their first run on native machines.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-10-05 11:08:03 +02:00
co-authored by Claude
parent 9bd237d89c
commit 0eb5608873
30 changed files with 431 additions and 447 deletions
+8 -7
View File
@@ -1,14 +1,16 @@
# Release matrix — INERT until act_runner instances with these labels are # Release matrix — INERT until act_runner instances with these labels are
# registered on git.teknes.es. Each OS builds its own artifacts (electrobun # registered on git.teknes.es. Each OS builds its own artifacts (electrobun
# cannot cross-compile: packaging needs codesign/hdiutil on macOS and # cannot cross-compile: packaging needs codesign/hdiutil on macOS and
# rcedit/PowerShell on Windows). # rcedit/PowerShell on Windows). No mac-x64: Electrobun 2.x has no macOS x64
# core.
# #
# Runner prerequisites: # Runner prerequisites:
# macos — arm64 Mac: bun, Xcode CLT, Rosetta 2 (softwareupdate # macos — Apple Silicon Mac: bun, Xcode CLT, network to github.com and the
# --install-rosetta), network to github.com (electrobun downloads # Electrobun/Hutch release hosts (`bun install` runs `electrobun
# its per-arch core binaries on first build) # prepare`; the first build per Electrobun version fills ~/.hutch
# windows — Windows 11 x64: bun, network to github.com; WebView2 runtime is # with Hutch, the core, Cottontail and the pinned Bun, ~200 MB)
# preinstalled on Win 11 # windows — Windows 11 x64: bun, the same network access; WebView2 runtime
# is preinstalled on Win 11
# #
# Trigger: push a tag like build-62 (git tag build-$(git rev-list --count HEAD)). # Trigger: push a tag like build-62 (git tag build-$(git rev-list --count HEAD)).
name: release name: release
@@ -26,7 +28,6 @@ jobs:
fetch-depth: 0 # full history — the build number is the commit count fetch-depth: 0 # full history — the build number is the commit count
- run: bun install --frozen-lockfile - run: bun install --frozen-lockfile
- run: bun scripts/release.ts --target mac-arm64 - run: bun scripts/release.ts --target mac-arm64
- run: bun scripts/release.ts --target mac-x64
- uses: actions/upload-artifact@v3 - uses: actions/upload-artifact@v3
with: with:
name: mac name: mac
+3
View File
@@ -8,3 +8,6 @@ ui/dist/
ui/.angular/ ui/.angular/
src/shared/version.gen.ts src/shared/version.gen.ts
release/ release/
# Hutch-generated (electrobun 2.x): the projected SDK devkit and build scratch
.hutch/
.cottontail-tmp/
+29 -26
View File
@@ -53,7 +53,7 @@ Dense reference material lives in [docs/](docs/): [architecture](docs/architectu
| Layer | Choice | | Layer | Choice |
| ---------- | ------------------------------------------------------------- | | ---------- | ------------------------------------------------------------- |
| Runtime | Bun (main process, all SSH/SFTP + file IO) | | Runtime | Bun (main process, all SSH/SFTP + file IO) |
| Desktop | Electrobun 1.18 (native webview, typed RPC bridge) | | Desktop | Electrobun 2.0 via Hutch (native webview, typed RPC bridge) |
| Frontend | Angular 22 (standalone, signals, zoneless) in `ui/` | | Frontend | Angular 22 (standalone, signals, zoneless) in `ui/` |
| Styling | Tailwind CSS v4, Oxide-derived tokens (no component library) | | Styling | Tailwind CSS v4, Oxide-derived tokens (no component library) |
| Icons | @ng-icons/tdesign-icons | | Icons | @ng-icons/tdesign-icons |
@@ -83,6 +83,10 @@ ui/src/app/ Angular components + signal services
Details and diagnostics in [platform notes](docs/platform-notes.md). Details and diagnostics in [platform notes](docs/platform-notes.md).
- No Node install required — a preload shim (`scripts/node-version-shim.ts`) - No Node install required — a preload shim (`scripts/node-version-shim.ts`)
satisfies the Angular CLI's Node version check while running under Bun. satisfies the Angular CLI's Node version check while running under Bun.
- Network on the first `bun install`: Electrobun 2.x's npm package is a
bootstrap that downloads its build tool (Hutch), the Electrobun core and the
pinned Bun runtime into `~/.hutch` (~200 MB), and projects the SDK into the
gitignored `.hutch/devkit/`.
## Setup ## Setup
@@ -107,22 +111,19 @@ Versioning is Sublime-style: **Build n**, where n is the commit count
`src/shared/version.gen.ts` by every build script (and `postinstall`). `src/shared/version.gen.ts` by every build script (and `postinstall`).
One artifact per invocation, on the matching host — electrobun cannot One artifact per invocation, on the matching host — electrobun cannot
cross-compile between OSes: cross-compile (Hutch builds for its own OS and arch):
```sh ```sh
bun run release -- --target mac-arm64 # on the Mac (native) bun run release -- --target mac-arm64 # on an Apple Silicon Mac
bun run release -- --target mac-x64 # on the same arm64 Mac, via Rosetta 2
bun run release -- --target win-x64 # on a Windows 11 x64 machine bun run release -- --target win-x64 # on a Windows 11 x64 machine
bun run release -- --target linux-x64 # on a Linux x64 machine bun run release -- --target linux-x64 # on a Linux x64 machine
``` ```
Artifacts land in `release/<target>/miniSCP-Build<n>-<target>.*` — a `.dmg` Artifacts land in `release/<target>/miniSCP-Build<n>-<target>.*` — a `.dmg`
per mac arch; on Windows a `-Setup.zip` installer **and** a `-portable.zip` on the Mac; on Windows a `-Setup.zip` installer **and** a `-portable.zip`
(unzip anywhere and run); on Linux a portable `.tar.gz` (unpack and run (unzip anywhere and run); on Linux Electrobun's `-Setup.tar.gz` installer.
`miniSCP.sh`, which sets the NVIDIA workaround then execs `bin/launcher`). There is no Intel Mac build: Electrobun 2.x publishes no macOS x64 core.
The mac-x64 build fetches electrobun's x64 CLI once CI: `.gitea/workflows/release.yml` runs the
into `~/.cache/miniscp/` and runs it under Rosetta so the whole pipeline
believes it is on an Intel Mac. CI: `.gitea/workflows/release.yml` runs the
same script on a macos + windows runner matrix once act_runners are same script on a macos + windows runner matrix once act_runners are
registered; trigger by pushing a `build-<n>` tag. registered; trigger by pushing a `build-<n>` tag.
@@ -137,7 +138,7 @@ compiled from `assets/icon.iconset` into `AppIcon.icns` automatically.
For a distributable (optimized, with auto-update channel) build, use For a distributable (optimized, with auto-update channel) build, use
`bunx electrobun build --env=stable` (see the `bunx electrobun build --env=stable` (see the
[Electrobun docs](https://framework.blackboard.sh/electrobun/) for signing, [Electrobun docs](https://electrobun.dev/electrobun/) for signing,
notarization, and release hosting). notarization, and release hosting).
## Rebranding ## Rebranding
@@ -233,11 +234,12 @@ electrobun APIs the app uses are implemented on Windows; known issues are
upstream: upstream:
- **HiDPI blur** ([electrobun#324](https://github.com/blackboardsh/electrobun/issues/324)): - **HiDPI blur** ([electrobun#324](https://github.com/blackboardsh/electrobun/issues/324)):
WebView2 renders at 1x on scaled displays until upstream adds DPI awareness. WebView2 rendered at 1x on scaled displays on Electrobun 1.18; 2.x ships
per-monitor DPI setup — to re-check on the first 2.x Windows build.
- **Notifications** use legacy balloon style, not Windows toast. - **Notifications** use legacy balloon style, not Windows toast.
- **Unsigned binaries**: both the Setup.exe installer and the portable exe - **Unsigned binaries**: both the Setup.exe installer and the portable exe
trigger SmartScreen/Mark-of-the-Web warnings on first run. No Start Menu trigger SmartScreen/Mark-of-the-Web warnings on first run. The installer
entry or uninstaller (installer unpacks to `%LOCALAPPDATA%`). unpacks to `%LOCALAPPDATA%` and, since Electrobun 2.x, ships an uninstaller.
- `connections.json` is protected with an `icacls` current-user ACL instead of - `connections.json` is protected with an `icacls` current-user ACL instead of
POSIX 0600 (which is meaningless on Windows). POSIX 0600 (which is meaningless on Windows).
@@ -249,7 +251,8 @@ portable zip runs from any folder.
## Linux (beta) ## Linux (beta)
Linux x64 is supported as a **beta** target and builds a portable tarball. Linux x64 is supported as a **beta** target and ships Electrobun's own
installer (`-Setup.tar.gz`).
One hard requirement: the AppIndicator library listed under One hard requirement: the AppIndicator library listed under
[Prerequisites](#prerequisites) — electrobun links it for the tray, so without [Prerequisites](#prerequisites) — electrobun links it for the tray, so without
it the app aborts at startup with `ERR_DLOPEN_FAILED` rather than merely losing it the app aborts at startup with `ERR_DLOPEN_FAILED` rather than merely losing
@@ -260,12 +263,12 @@ its tray icon.
HTML, so nothing is missing in practice. HTML, so nothing is missing in practice.
- **Tray visibility** depends on the desktop: KDE, XFCE, MATE and Cinnamon show - **Tray visibility** depends on the desktop: KDE, XFCE, MATE and Cinnamon show
it; GNOME 3.26+ needs the AppIndicator extension. The app works either way. it; GNOME 3.26+ needs the AppIndicator extension. The app works either way.
- **Unsigned and unpackaged**: no AppImage, `.deb`, desktop entry or icon - **Unsigned**, and no AppImage or `.deb` — unpack the `-Setup.tar.gz` and run
installation — unpack the tarball and run `miniSCP.sh`. its `installer`.
- **NVIDIA GPUs** need `WEBKIT_DISABLE_DMABUF_RENDERER=1` or the window renders - **NVIDIA GPUs** need `WEBKIT_DISABLE_DMABUF_RENDERER=1` or the window renders
black. Every launch path (dev scripts and the packaged `miniSCP.sh` wrapper) black. The dev scripts set it automatically when an NVIDIA driver is
sets it automatically when an NVIDIA driver is detected — see detected; an installed build needs it set by hand (e.g. in its desktop
[gotchas](docs/gotchas.md#linux) if you add a new way to launch the app. entry) — see [gotchas](docs/gotchas.md#linux).
Full dependency table, diagnostics and the first-boot smoke test: Full dependency table, diagnostics and the first-boot smoke test:
[platform notes](docs/platform-notes.md). [platform notes](docs/platform-notes.md).
@@ -273,17 +276,17 @@ Full dependency table, diagnostics and the first-boot smoke test:
## Known issues ## Known issues
- **Rare native crash under load.** A long-running session (multiple remote - **Rare native crash under load.** A long-running session (multiple remote
connections, extended uptime) can hit a Bun panic — a segfault inside Bun connections, extended uptime) could hit a Bun panic — a segfault inside Bun
1.3.13's FFI thread-safe callbacks executing Electrobun's native→worker 1.3.13's FFI thread-safe callbacks executing Electrobun's native→worker
bridge (`FFI_Callback_threadsafe_call` → `web_worker.spin` in the decoded bridge (`FFI_Callback_threadsafe_call` → `web_worker.spin` in the decoded
crash report). This is an upstream Bun × Electrobun bug, not app code; a crash report). This is an upstream Bun × Electrobun bug, not app code; a
restart recovers cleanly and in-flight work isn't corrupted. Expected to restart recovers cleanly and in-flight work isn't corrupted. Electrobun 2.0.2
resolve when Electrobun bumps its pinned Bun (currently 1.3.13; 1.3.14+ pins Bun 1.4.0, which may have fixed it — not yet confirmed under load. Bun's
available). Bun's own crash reporter link is printed to the console on panic. own crash reporter link is printed to the console on panic.
## Not in v1 ## Not in v1
Drag-and-drop, file preview/edit, transfer resume, symlink following Drag-and-drop, file preview/edit, transfer resume, symlink following
(symlinks are listed but skipped by transfers), OS keychain, tab persistence (symlinks are listed but skipped by transfers), OS keychain, tab persistence
across restarts, Linux desktop integration (AppImage/.deb/.desktop entry — across restarts, Linux AppImage/.deb packages (the release is Electrobun's
the release is a portable tarball). Setup installer).
+11 -63
View File
@@ -5,7 +5,6 @@
"": { "": {
"name": "bunscp", "name": "bunscp",
"dependencies": { "dependencies": {
"electrobun": "1.18.1",
"ssh-config": "^5.2.0", "ssh-config": "^5.2.0",
"ssh2": "^1.17.0", "ssh2": "^1.17.0",
}, },
@@ -13,6 +12,7 @@
"@resvg/resvg-js": "^2.6.2", "@resvg/resvg-js": "^2.6.2",
"@types/bun": "latest", "@types/bun": "latest",
"@types/ssh2": "^1.15.5", "@types/ssh2": "^1.15.5",
"electrobun": "2.0.2",
"png-to-ico": "^3.0.2", "png-to-ico": "^3.0.2",
"typescript": "~5.9.3", "typescript": "~5.9.3",
}, },
@@ -133,8 +133,6 @@
"@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="],
"@babylonjs/core": ["@babylonjs/core@7.54.3", "", {}, "sha512-P5ncXVd8GEUJLhwloP9V0oVwQYIrvZztguVeLlvd5Rx+9aQnenKjpV8auJ6SRsUlAmNZU4pFTKzwF6o2EUfhAw=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="],
@@ -253,8 +251,6 @@
"@lmdb/lmdb-win32-x64": ["@lmdb/lmdb-win32-x64@3.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-JF1BmLCm9kGEVZgYmJq43zeQVdHVgAJnTi/NURWEsy6L1ZrrlSmdltS+D17QN4LODwf+1LMXAA9auIZVXtWwzw=="], "@lmdb/lmdb-win32-x64": ["@lmdb/lmdb-win32-x64@3.5.4", "", { "os": "win32", "cpu": "x64" }, "sha512-JF1BmLCm9kGEVZgYmJq43zeQVdHVgAJnTi/NURWEsy6L1ZrrlSmdltS+D17QN4LODwf+1LMXAA9auIZVXtWwzw=="],
"@malept/cross-spawn-promise": ["@malept/cross-spawn-promise@1.1.1", "", { "dependencies": { "cross-spawn": "^7.0.1" } }, "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ=="],
"@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.29.0", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.2.1", "express-rate-limit": "^8.2.1", "hono": "^4.11.4", "jose": "^6.1.3", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.1" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="], "@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LCkGo6JDfaBhgST7UpPWgNgLINpcpabaHfyz5OBx75nUYxBsaEPxjnyNjWpeb/xBup/682QnBfRBy2/LvPutZQ=="],
@@ -475,8 +471,6 @@
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.2", "@tailwindcss/oxide": "4.3.2", "postcss": "^8.5.15", "tailwindcss": "4.3.2" } }, "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g=="], "@tailwindcss/postcss": ["@tailwindcss/postcss@4.3.2", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.3.2", "@tailwindcss/oxide": "4.3.2", "postcss": "^8.5.15", "tailwindcss": "4.3.2" } }, "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g=="],
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
"@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="], "@tufjs/canonical-json": ["@tufjs/canonical-json@2.0.0", "", {}, "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA=="],
"@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="], "@tufjs/models": ["@tufjs/models@4.1.0", "", { "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^10.1.1" } }, "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww=="],
@@ -497,7 +491,7 @@
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
"agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], "agent-base": ["agent-base@9.0.0", "", {}, "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA=="],
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
@@ -513,14 +507,10 @@
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="], "asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
"ast-types": ["ast-types@0.13.4", "", { "dependencies": { "tslib": "^2.0.1" } }, "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w=="],
"balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.42", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q=="],
"basic-ftp": ["basic-ftp@5.3.1", "", {}, "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw=="],
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="], "bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
"beasties": ["beasties@0.4.2", "", { "dependencies": { "css-select": "^6.0.0", "css-what": "^7.0.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "htmlparser2": "^10.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.49", "postcss-media-query-parser": "^0.2.3", "postcss-safe-parser": "^7.0.1" } }, "sha512-NvcGjG/7AVUAfRbvrJmHunDQS9uHnE6Q/7AkaPr8oKE8HjOlpjRG5075z/th2Tmlezk3VlaaS8+X9I1RwHJMQw=="], "beasties": ["beasties@0.4.2", "", { "dependencies": { "css-select": "^6.0.0", "css-what": "^7.0.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "htmlparser2": "^10.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.49", "postcss-media-query-parser": "^0.2.3", "postcss-safe-parser": "^7.0.1" } }, "sha512-NvcGjG/7AVUAfRbvrJmHunDQS9uHnE6Q/7AkaPr8oKE8HjOlpjRG5075z/th2Tmlezk3VlaaS8+X9I1RwHJMQw=="],
@@ -583,18 +573,12 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"cross-spawn-windows-exe": ["cross-spawn-windows-exe@1.2.0", "", { "dependencies": { "@malept/cross-spawn-promise": "^1.1.0", "is-wsl": "^2.2.0", "which": "^2.0.2" } }, "sha512-mkLtJJcYbDCxEG7Js6eUnUNndWjyUZwJ3H7bErmmtOYU/Zb99DyUkpamuIZE0b3bhmJyZ7D90uS6f+CGxRRjOw=="],
"css-select": ["css-select@6.0.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^7.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "nth-check": "^2.1.1" } }, "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw=="], "css-select": ["css-select@6.0.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^7.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "nth-check": "^2.1.1" } }, "sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw=="],
"css-what": ["css-what@7.0.0", "", {}, "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ=="], "css-what": ["css-what@7.0.0", "", {}, "sha512-wD5oz5xibMOPHzy13CyGmogB3phdvcDaB5t0W/Nr5Z2O/agcB8YwOz6e2Lsp10pNDzBoDO9nVa3RGs/2BttpHQ=="],
"data-uri-to-buffer": ["data-uri-to-buffer@6.0.2", "", {}, "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
@@ -611,7 +595,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"electrobun": ["electrobun@1.18.1", "", { "dependencies": { "@babylonjs/core": "^7.45.0", "@types/bun": "^1.3.8", "png-to-ico": "^2.1.8", "proxy-agent": "^6.5.0", "rcedit": "^4.0.1", "three": "^0.165.0" }, "bin": { "electrobun": "bin/electrobun.cjs" } }, "sha512-tgZ+WKGskn/1/Y5i1mpVCCkgRa1O31Tz7MIArBTK44GfPzT43uOq9c/HvxdQGrLeZV8ZvjK1lQrwqSXCgh6tvA=="], "electrobun": ["electrobun@2.0.2", "", { "bin": { "electrobun": "bin/electrobun.cjs" } }, "sha512-UDs2iTNs6qYCzdWiFch/0DtxrsijNDr1WFDQfRmSA3Iouv0iEzyCF5mh4uDxkwNnAbuwFyow36EiVTYP4DRzig=="],
"electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="], "electron-to-chromium": ["electron-to-chromium@1.5.389", "", {}, "sha512-cEto7aeOqBfU1D+c5py5pE+ooscKE75JifxLBdFUZsqAxRS6y7kebtxAZvICszSl05gPjYHDTjY+lXpyGvpJbg=="],
@@ -639,14 +623,6 @@
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
"esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], "eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
@@ -695,8 +671,6 @@
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-uri": ["get-uri@6.0.5", "", { "dependencies": { "basic-ftp": "^5.0.2", "data-uri-to-buffer": "^6.0.2", "debug": "^4.3.4" } }, "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg=="],
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="],
@@ -737,8 +711,6 @@
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-docker": ["is-docker@2.2.1", "", { "bin": { "is-docker": "cli.js" } }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
@@ -751,8 +723,6 @@
"is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="],
"is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
"jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="],
@@ -807,7 +777,7 @@
"log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="],
"lru-cache": ["lru-cache@7.18.3", "", {}, "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="], "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
@@ -859,8 +829,6 @@
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], "negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
"netmask": ["netmask@2.1.1", "", {}, "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA=="],
"node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="], "node-addon-api": ["node-addon-api@6.1.0", "", {}, "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="],
"node-gyp": ["node-gyp@12.4.0", "", { "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" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="], "node-gyp": ["node-gyp@12.4.0", "", { "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" } }, "sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw=="],
@@ -903,10 +871,6 @@
"p-map": ["p-map@7.0.5", "", {}, "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA=="], "p-map": ["p-map@7.0.5", "", {}, "sha512-e8vJF4XdVkzqqSHguEMz41mQO1wKwxKm5ENrUJQUu9kLDCtn83cxbyHZcszr4QC5zEA7WffRRC4gsTecC7J9oA=="],
"pac-proxy-agent": ["pac-proxy-agent@7.2.0", "", { "dependencies": { "@tootallnate/quickjs-emscripten": "^0.23.0", "agent-base": "^7.1.2", "debug": "^4.3.4", "get-uri": "^6.0.1", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.6", "pac-resolver": "^7.0.1", "socks-proxy-agent": "^8.0.5" } }, "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA=="],
"pac-resolver": ["pac-resolver@7.0.1", "", { "dependencies": { "degenerator": "^5.0.0", "netmask": "^2.0.2" } }, "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg=="],
"pacote": ["pacote@21.5.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="], "pacote": ["pacote@21.5.1", "", { "dependencies": { "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", "@npmcli/promise-spawn": "^9.0.0", "@npmcli/run-script": "^10.0.0", "cacache": "^20.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^13.0.0", "npm-packlist": "^10.0.1", "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" }, "bin": { "pacote": "bin/index.js" } }, "sha512-KvcJ9iy3crysCsgqc4+PknH/w6jkrp8JN36mpZBPwNaDRwTfMZD37YzRazNstiZUOhuF5pno9f78n9mEJBavwg=="],
"parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="], "parse5": ["parse5@8.0.1", "", { "dependencies": { "entities": "^8.0.0" } }, "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw=="],
@@ -945,18 +909,12 @@
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
"proxy-agent": ["proxy-agent@6.5.0", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "^4.3.4", "http-proxy-agent": "^7.0.1", "https-proxy-agent": "^7.0.6", "lru-cache": "^7.14.1", "pac-proxy-agent": "^7.1.0", "proxy-from-env": "^1.1.0", "socks-proxy-agent": "^8.0.5" } }, "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A=="],
"proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="],
"qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="], "qs": ["qs@6.15.3", "", { "dependencies": { "es-define-property": "^1.0.1", "side-channel": "^1.1.1" } }, "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A=="],
"range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="], "range-parser": ["range-parser@1.3.0", "", {}, "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw=="],
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
"rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="],
@@ -1041,8 +999,6 @@
"tar": ["tar@7.5.19", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw=="], "tar": ["tar@7.5.19", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw=="],
"three": ["three@0.165.0", "", {}, "sha512-cc96IlVYGydeceu0e5xq70H8/yoVT/tXBxV/W8A/U6uOq7DXc4/s1Mkmnu6SqoYGhSRWWYFOhVwvq6V0VtbplA=="],
"tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="],
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
@@ -1105,10 +1061,10 @@
"@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
"@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@npmcli/agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"@npmcli/agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "@npmcli/agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"@npmcli/agent/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "@npmcli/agent/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
@@ -1145,18 +1101,18 @@
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"electrobun/png-to-ico": ["png-to-ico@2.1.8", "", { "dependencies": { "@types/node": "^17.0.36", "minimist": "^1.2.6", "pngjs": "^6.0.0" }, "bin": { "png-to-ico": "bin/cli.js" } }, "sha512-Nf+IIn/cZ/DIZVdGveJp86NG5uNib1ZXMiDd/8x32HCTeKSvgpyg6D/6tUBn1QO/zybzoMK0/mc3QRgAyXdv9w=="],
"hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "hosted-git-info/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
"https-proxy-agent/agent-base": ["agent-base@9.0.0", "", {}, "sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA=="], "http-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], "log-update/slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
"log-update/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], "log-update/wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
"lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
"minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="],
@@ -1165,16 +1121,14 @@
"ora/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="], "ora/string-width": ["string-width@8.2.2", "", { "dependencies": { "get-east-asian-width": "^1.5.0", "strip-ansi": "^7.1.2" } }, "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg=="],
"pac-proxy-agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], "path-scurry/lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="],
"png-to-ico/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="], "png-to-ico/@types/node": ["@types/node@25.9.5", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-OScDchr2fwuUmWdf4kZ9h7PcJiYDVInhJizG/biAq3cAvqwYktuy/TYGGdZNMtNTFUP7rnb0NU4TUdm82kt4Rg=="],
"proxy-agent/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "sass/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"socks-proxy-agent/agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
"type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="], "type-is/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
"ui/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "ui/typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
@@ -1187,16 +1141,10 @@
"@angular/compiler-cli/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@angular/compiler-cli/@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@npmcli/git/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], "@npmcli/git/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="],
"@npmcli/promise-spawn/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="], "@npmcli/promise-spawn/which/isexe": ["isexe@4.0.0", "", {}, "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="],
"electrobun/png-to-ico/@types/node": ["@types/node@17.0.45", "", {}, "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw=="],
"electrobun/png-to-ico/pngjs": ["pngjs@6.0.0", "", {}, "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg=="],
"minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "minipass-flush/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
"minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], "minipass-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
+26 -14
View File
@@ -6,15 +6,21 @@ Dense reference for development and debugging. Companion files:
## Process model ## Process model
- **Bun main process** (`src/bun/`): electrobun runs it inside a WebWorker of - **Bun main process** (`src/bun/`): the native launcher runs electrobun's
its native launcher. Owns every privileged capability: filesystem, SSH, `main.js` on the Bun the 2.x devkit pins (1.4.0, shipped in the bundle —
window management, native menus/dialogs/tray, the connection/settings stores. `build.mainProcess: "bun"`), which starts our bundle in a Worker. Imports the
SDK as `electrobun/main` (projected into `.hutch/devkit/`). Owns every
privileged capability: filesystem, SSH, window management, native
menus/dialogs/tray, the connection/settings stores.
- **Webviews** (`ui/` Angular): one per window, system WebKit on macOS / - **Webviews** (`ui/` Angular): one per window, system WebKit on macOS /
WebView2 on Windows / WebKitGTK on Linux. Zero privileged access — everything WebView2 on Windows / WebKitGTK on Linux. Zero privileged access — everything
goes through RPC. goes through RPC.
- **RPC transport**: one WebSocket server in the bun process, multiplexed by - **RPC transport**: one WebSocket server in the bun process, multiplexed by
`webviewId` (each webview auto-connects using natively injected globals). `webviewId` (each webview auto-connects using natively injected globals;
Requests are typed by the `AppRPC` schema in `src/shared/rpc.ts`. packets AES-GCM-encrypted per webview). 2.x Electroview queues sends while
its `hostSocket` connects and falls back to a native bridge when the socket
is unavailable. Requests are typed by the `AppRPC` schema in
`src/shared/rpc.ts`.
### The one-schema rule ### The one-schema rule
@@ -39,7 +45,9 @@ Settings window). `rpc.send.*` on an instance targets exactly its own webview.
exist until after construction). `AppDeps.events` is a mutable object of exist until after construction). `AppDeps.events` is a mutable object of
no-op functions, reassigned in `src/bun/index.ts` after everything exists: no-op functions, reassigned in `src/bun/index.ts` after everything exists:
`sessionClosed`, `connectionsChanged`, `openEditor`, `settingsChanged`, `sessionClosed`, `connectionsChanged`, `openEditor`, `settingsChanged`,
`openConnectionManager`, `openSettings`, `showAbout`. `openConnectionManager`, `openSettings`, `showAbout`, `applyUpdate` (the
update restart lives in index.ts beside the before-quit guard it must get past —
see build-and-release.md "Auto-updater").
## Window kinds ## Window kinds
@@ -50,9 +58,9 @@ scale so the first paint is already right. `ui/src/main.ts` picks the root
component from `APP_MODE`; all four roots use selector `app-root` (index.html component from `APP_MODE`; all four roots use selector `app-root` (index.html
hosts only that tag). `chromeOptions()` is the ONLY place the native frame is hosts only that tag). `chromeOptions()` is the ONLY place the native frame is
decided, spread by the main window and `openSecondaryWindow`: macOS `hiddenInset` + decided, spread by the main window and `openSecondaryWindow`: macOS `hiddenInset` +
`trafficLightOffset {x:12,y:13}` (the UI renders an `h-10 pl-24` drag-region `trafficLightOffset {x:12,y:4}` (2.x semantics — see gotchas; the UI renders an
header via `IS_MAC_CHROME`); other platforms keep the native titlebar and a `h-10 pl-24` drag-region header via `IS_MAC_CHROME`); other platforms keep the
compact `h-9` non-drag header. native titlebar and a compact `h-9` non-drag header.
`WindowRegistry<Rec>` (same file): keyed window map with dedupe/activate, `WindowRegistry<Rec>` (same file): keyed window map with dedupe/activate,
cascade placement off an anchor frame, focus tracking. electrobun's emitter cascade placement off an anchor frame, focus tracking. electrobun's emitter
@@ -67,8 +75,9 @@ through a concrete session, and `local` is one shared session anyway).
Re-opening focuses the existing window. The webview fetches its own content Re-opening focuses the existing window. The webview fetches its own content
(`readFile`) — `openEditor` just creates the window with ctx in the preload. (`readFile`) — `openEditor` just creates the window with ctx in the preload.
**Close safety** (electrobun window `close` is NOT cancellable — verified, no **Close safety** (designed when electrobun window `close` was NOT cancellable —
`allow` response like `before-quit` has): 1.18.1 had no `allow` response like `before-quit`; 2.x adds a cancellable
`will-close` for the titlebar close, not used yet — see gotchas):
1. ⌘W (routed via the app menu → `editorCloseRequest`) / Close button → 1. ⌘W (routed via the app menu → `editorCloseRequest`) / Close button →
styled in-window Save/Don't Save/Cancel; the confirm path calls styled in-window Save/Don't Save/Cancel; the confirm path calls
`editorClose` which sets `suppressPrompt` before `win.close()`. `editorClose` which sets `suppressPrompt` before `win.close()`.
@@ -92,7 +101,9 @@ not `findInPage`, which is a shim on WebView2).
### Connection-manager window (`src/bun/manager-window.ts`) ### Connection-manager window (`src/bun/manager-window.ts`)
Single instance, centered over main, **modal to main only** (editors stay Single instance, centered over main, **modal to main only** (editors stay
usable). No native modality exists, so three layers approximate it: bun usable). No native modality exists (re-checked on 2.0.2: no parent/owner/sheet
option or input lock in any SDK or the core's exports; `styleMask.DocModalWindow`
is only the cosmetic NSWindow bit), so three layers approximate it: bun
bounces main's `focus` event back to the manager; the main webview renders a bounces main's `focus` event back to the manager; the main webview renders a
click-swallowing overlay (`managerState` message); main drops its keyboard click-swallowing overlay (`managerState` message); main drops its keyboard
shortcuts and bun drops main-targeted menu actions while open. Connect / shortcuts and bun drops main-targeted menu actions while open. Connect /
@@ -208,8 +219,9 @@ component; tokens → `ui/src/styles.css`.
through plain callback registries, never signals**: a signal consumed from through plain callback registries, never signals**: a signal consumed from
an effect that also writes signals re-triggers itself (this froze the whole an effect that also writes signals re-triggers itself (this froze the whole
app once), and signal coalescing drops progress snapshots. All requests are app once), and signal coalescing drops progress snapshots. All requests are
gated on the socket-open promise — calls fired during boot would otherwise gated on the socket-open promise (`electroview.hostSocket`) — on 1.18.1 calls
be silently lost. `request` and `send` are ready-gated Proxies. fired during boot were silently lost; 2.x Electroview queues them itself, so
the gate is belt-and-braces now. `request` and `send` are ready-gated Proxies.
- `panels.service.ts` — `PanelState` (per panel): sessionId, fsKey, cwd, - `panels.service.ts` — `PanelState` (per panel): sessionId, fsKey, cwd,
entries, sort, cursor, selection, type-to-filter. `sorted` computed: entries, sort, cursor, selection, type-to-filter. `sorted` computed:
filter → dirs-first sort (name/size/mtime numeric, kind/rights/owner/group filter → dirs-first sort (name/size/mtime numeric, kind/rights/owner/group
+113 -79
View File
@@ -14,81 +14,101 @@ semver-shaped (`0.1.<n>`) for electrobun's config/updater; `BRANDING.buildLabel`
## Dev loop ## Dev loop
- `bun run dev` — gen-version → scripts/dev.ts: starts `ng serve` (HMR) and - `bun run dev` — gen-version → `electrobun prepare` → scripts/dev.ts: starts
`electrobun dev --watch`, with `DEV_SERVER_URL` pointing the webviews at the `ng serve` (HMR) and `electrobun dev --watch`, with `DEV_SERVER_URL` pointing
dev server (only honored on the dev channel and only if the server answers). the webviews at the dev server (only honored on the dev channel and only if
- `bun run build` — gen-version → Angular build → `electrobun build` the server answers).
(**dev channel** — fine for boot checks, NOT a release). - `bun run build` — gen-version → `electrobun prepare` → Angular build →
`electrobun build` (**dev channel** — fine for boot checks, NOT a release).
- `scripts/node-version-shim.ts` (preloaded via `bunfig.toml`, root AND ui/) - `scripts/node-version-shim.ts` (preloaded via `bunfig.toml`, root AND ui/)
fakes a Node version the Angular CLI accepts under Bun. Both preloads are fakes a Node version the Angular CLI accepts under Bun. Both preloads are
load-bearing. load-bearing.
- `scripts/fix-electrobun-cli.ts` re-signs electrobun's prebuilt CLI binary, - **Electrobun 2.x is driven by Hutch.** The `electrobun` devDependency (exact
whose upstream ad-hoc signature is invalid — macOS SIGKILLs it and the app `2.0.2`) is only a bootstrap: its first command downloads the paired Hutch
silently never launches, with exit code 0 (see [gotchas.md](gotchas.md)). It (0.27.1) from the Electrobun GitHub release, checks its SHA-256 against
runs from `postinstall` **and** at the start of `dev`/`start`/`build`/ `hutch-artifacts.json`, caches it under `~/.hutch/npm/electrobun/<ver>/`, and
`release`, because the CLI is downloaded lazily on first use rather than at forwards every `electrobun …` to `hutch electrobun …`. Hutch then fetches the
install time; when the binary is missing it provokes that download first, so Electrobun core, its build-time Cottontail and the pinned Bun 1.4.0 (the
a fresh checkout self-heals. Equally load-bearing — removing it means runtime the app ships) into `~/.hutch` (~200 MB on a fresh machine) and
`bun run start` does nothing at all. projects the SDK into the gitignored `.hutch/devkit/` (plus a gitignored
`.cottontail-tmp/` scratch dir). The devDependency version IS the toolchain
pin: moving Electrobun = bump it + `bun install`. No Node needed — the
bootstrap's `#!/usr/bin/env node` runs under `bunx`.
- `bunx electrobun prepare` (re)projects `.hutch/devkit`. It runs from
`postinstall` **and** at the start of `dev`/`start`/`build`/`release` (~0.5 s
when current — the slot the v1 CLI re-sign step held; that step is gone,
Hutch's binaries carry valid signatures). tsc (the root tsconfig extends
`.hutch/devkit/tsconfig.json`) and the Angular build / `ng serve` (the
`electrobun/view` path in `ui/tsconfig.json`) resolve the SDK there *before*
any electrobun command would project it; without it both fail on unresolved
`electrobun/*` imports. Bun stays the package manager: Hutch's own resolver
(`hutch.lock`, no lifecycle scripts) only runs on `hutch install`, which
nothing calls — hence no `hutch.config.ts`.
## Release builds ## Release builds
`bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64]` `bun scripts/release.ts [--target mac-arm64|win-x64|linux-x64]`
(default: host). (default: host).
Steps: gen-version → fix-electrobun-cli → gen-icons → Angular build → Steps: gen-version → electrobun prepare → gen-icons → Angular build → empty
`electrobun build --env=stable` → copy/rename artifacts into `artifacts/` → `electrobun build --env=stable` → copy/rename the installers into
`release/<target>/miniSCP-Build<n>-<target>.*`. `release/<target>/miniSCP-Build<n>-<target>.*`.
Facts the script encodes: Facts the script encodes:
- **`--env=stable` is mandatory for releases** — the CLI defaults to the dev - **`--env=stable` is mandatory for releases** — `electrobun build` defaults to
channel (different app name `miniSCP-dev`, no distributable artifacts). the dev channel (different app name `miniSCP-dev`, no distributable artifacts).
- **No OS cross-compile.** The CLI builds for its host (its `targets` config - **No cross-compile, and no Intel Mac.** Hutch builds for its host OS *and*
field is unread in 1.18.1); packaging needs host tools (codesign/hdiutil on arch only; packaging needs host tools (codesign/hdiutil on macOS,
macOS, rcedit/PowerShell on Windows). Hence one script run per artifact, on rcedit/PowerShell on Windows). Hence one script run per artifact, on the
the matching machine; `win-x64` on a non-Windows host and `linux-x64` on a matching machine; `win-x64` on a non-Windows host and `linux-x64` on a
non-Linux host both error out. non-Linux host both error out. **Electrobun 2.x publishes no macOS x64 core
- **mac-x64 from the arm64 Mac (Rosetta)**: the script downloads electrobun's (nor an x64 Hutch)**, so mac-x64 was dropped — the v1 trick of running the x64
x64 CLI binary once (`electrobun-cli-darwin-x64.tar.gz` from the GitHub CLI under Rosetta has nothing left to run. `--target mac-x64`, and any build
release matching the installed version, cached at on an Intel Mac, fail with that message.
`~/.cache/miniscp/electrobun-cli-x64-<ver>/`) and runs it directly — - **Runtime downloads**: on first use per Electrobun version Hutch fetches the
Rosetta translates it, the CLI sees `process.arch === "x64"`, downloads the platform core from `github.com/blackboardsh/electrobun/releases`
`dist-macos-x64` core into the electrobun package dir on first use, and (SHA-256-checked against `electrobun-artifacts.json`), plus Cottontail and the
packages a genuine x64 app. Note: Bun's `fetch`→`Bun.write` streaming hung pinned Bun, into `~/.hutch` — network is a build-time requirement on every
on the GitHub redirect chain; the script uses `curl -fsSL` deliberately. fresh machine/runner.
- **Both CLI binaries need re-signing.** The x64 CLI fetched for the Rosetta - **`artifacts/` is emptied before the build** — `site/deploy.sh` publishes it
path carries the same broken upstream signature as the arm64 one, so verbatim, so it must hold exactly this build (stale files, e.g. v1-named
`x64Cli()` re-signs it after download *and* on cache reuse; the native path installers, would otherwise ship).
is covered by the `fix-electrobun-cli.ts` step up front. - **Artifact shapes** (stable channel, in `artifacts/`): 2.x installers carry
- **Core binary downloads**: the installed npm package ships mac-arm64 **no channel prefix**; only the updater feed keeps `stable-<os>-<arch>-` (the
binaries only; other `dist-<os>-<arch>` cores are fetched from names v1.18.1+ clients request). macOS → `macos-arm64-miniSCP.dmg` +
`github.com/blackboardsh/electrobun/releases` at build time — network to `stable-macos-arm64-miniSCP.app.tar.zst` + `stable-macos-arm64-update.json`
GitHub is a build-time requirement on every fresh machine/runner. (verified); Windows → `win-x64-miniSCP-Setup.zip` (setup exe + hidden payload)
- **Artifact shapes** (stable channel, in `artifacts/`): macOS → + the `stable-win-x64-*` feed, plus the script's own **portable zip** of
`stable-macos-<arch>-miniSCP.dmg` + `.app.tar.zst` + `update.json` `build/stable-win-x64/miniSCP` (warns instead of failing if that folder is
(updater feed); Windows → `stable-win-x64-miniSCP-Setup.zip` missing); Linux → `linux-x64-miniSCP-Setup.tar.gz` (a self-extracting
(self-extracting installer → `%LOCALAPPDATA%`) plus the script's own `installer` + README) + the `stable-linux-x64-*` feed. The official Linux
**portable zip** made from the assembled app folder under installer IS the release artifact — the v1 portable tarball and its
`build/stable-win-x64/` (layout to confirm on the first real Windows build — `miniSCP.sh` NVIDIA wrapper are gone, so an installed build on NVIDIA needs
the step warns instead of failing); Linux → only `WEBKIT_DISABLE_DMABUF_RENDERER=1` from the user (see gotchas.md). The
`stable-linux-x64-miniSCP.tar.zst` + `update.json`, i.e. the updater feed and Windows/Linux names come from the 2.x docs — confirm on the first real 2.x
nothing installable, so the script's **`.tar.gz` of the assembled app folder** build there. A build that can reach the previous release at
under `build/stable-linux-x64/` IS the release artifact (same best-effort `release.baseUrl` also emits `stable-<os>-<arch>-<previous-hash>.patch`.
warn). No AppImage/.deb/.desktop — unpack and run the `miniSCP.sh` wrapper the
script writes beside `bin/launcher` (it applies the NVIDIA/DMABUF workaround,
which the app cannot set for itself — see gotchas.md).
- **Linux builds need the AppIndicator dev libraries present to RUN** (not to - **Linux builds need the AppIndicator dev libraries present to RUN** (not to
build) — see platform-notes.md; a packaged tarball started on a machine build) — see platform-notes.md; an installed build started on a machine
without them dies at `dlopen`. without them dies at `dlopen`.
- The script filters artifacts by app name, so stale artifacts from older - The script filters artifacts by app name, so stale artifacts from older
brandings don't get collected. brandings don't get collected.
- **v1 → 2.x bridge**: Windows users on 0.1.78 (the only feed live on
miniscp.com/dl) update through their v1 updater because `app.name`,
`app.identifier` and `release.baseUrl` are unchanged, the build is
`--env=stable`, and `artifacts/` is published unrenamed. 2.x keeps the v1
top-level `update.json` fields (`version`/`hash`/`platform`/`arch`) and the
physical `stable` state root, so user data stays put. Unverified until the
first 2.x Windows release reaches a v1 install.
## CI (Gitea Actions) ## CI (Gitea Actions)
`.gitea/workflows/release.yml` — tag-triggered (`build-*`), macos + windows `.gitea/workflows/release.yml` — tag-triggered (`build-*`), macos (mac-arm64) +
runner matrix, each running the same release script; inert until act_runners windows (win-x64) runner matrix, each running the same release script; inert
with those labels are registered. Needs `fetch-depth: 0` (the build number is until act_runners with those labels are registered. Needs `fetch-depth: 0` (the
the commit count). Runner prereqs are commented in the workflow file. build number is the commit count); `bun install --frozen-lockfile` already
prepares the devkit (postinstall). Runner prereqs are commented in the workflow
file.
## Download host (`site/` + Caddy) ## Download host (`site/` + Caddy)
@@ -96,27 +116,31 @@ the commit count). Runner prereqs are commented in the workflow file.
internal box (plain HTTP `:80`, no domain/TLS). Deploys to `/srv/miniscp/`. internal box (plain HTTP `:80`, no domain/TLS). Deploys to `/srv/miniscp/`.
- `site/index.html` — pure static, no JS, tinygrad-minimal. Download buttons link to **fixed - `site/index.html` — pure static, no JS, tinygrad-minimal. Download buttons link to **fixed
stable filenames** (`/dl/stable-macos-arm64-miniSCP.dmg`, …) so the page never changes per stable filenames** (`/dl/macos-arm64-miniSCP.dmg`, `/dl/win-x64-miniSCP-Setup.zip`, the portable
release. Logo/screenshot `<img>` slots are commented in, images live in `site/assets/`. zip) so the page never changes per release. 2.x dropped the `stable-` prefix from installer
names (the feed kept it), so the v1 `stable-…-Setup.zip`/`.dmg` installers still on the server
are stale — delete them by hand. Logo/screenshot `<img>` slots are commented in, images live in
`site/assets/`.
- `site/Caddyfile` — one flat `/dl/*` folder with `file_server browse` + range requests, and - `site/Caddyfile` — one flat `/dl/*` folder with `file_server browse` + range requests, and
per-type `Cache-Control`: `*.patch` immutable (unique hashed names), `*update.json` `no-cache` per-type `Cache-Control`: `*.patch` immutable (unique hashed names), `*update.json` `no-cache`
(updater must see new versions), installers (`*.dmg *.zip *.tar.zst`) `max-age=300` (stable (updater must see new versions), installers (`*.dmg *.zip *.tar.zst *.tar.gz`) `max-age=300`
names overwritten each release). Landing page has **no** SPA fallback → unknown paths 404. (stable names overwritten each release). Landing page has **no** SPA fallback → unknown paths 404.
Path matcher gotcha: use suffix globs like `*update.json`, not `*/*update.json`. Path matcher gotcha: use suffix globs like `*update.json`, not `*/*update.json`.
- **One flat folder, no per-build dirs** — this layout IS electrobun's `artifacts/` feed, so it - **One flat folder, no per-build dirs** — this layout IS electrobun's `artifacts/` feed, so it
doubles as the (future) updater `baseUrl`. Publishing = drop this machine's `artifacts/stable-*` doubles as the updater `baseUrl`. Publishing = drop this machine's whole `artifacts/` into
into `/dl/`; installers overwrite in place, `.patch` files accumulate (never delete them, or `/dl/`, unrenamed; installers overwrite in place, `.patch` files accumulate (never delete them,
older installs lose their incremental-update trail). **Tradeoff:** only the latest full or older installs lose their incremental-update trail). **Tradeoff:** only the latest full
installer is downloadable; old full versions are overwritten. installer is downloadable; old full versions are overwritten.
- `site/deploy.sh` — run per build machine after `scripts/release.ts`; copies `artifacts/stable-*` - `site/deploy.sh` — run per build machine after `scripts/release.ts`; copies `artifacts/*`
and renames the build-numbered win portable zip to the stable `stable-win-x64-miniSCP-portable.zip`. verbatim and renames the build-numbered win portable zip to the stable
`MINISCP_DL_DIR` overrides the target (stage locally, then rsync to the server). `stable-win-x64-miniSCP-portable.zip`. `MINISCP_DL_DIR` overrides the target (stage locally,
then rsync to the server).
- `release.baseUrl = https://miniscp.com/dl` is set in `electrobun.config.ts`; `miniscp.com` is - `release.baseUrl = https://miniscp.com/dl` is set in `electrobun.config.ts`; `miniscp.com` is
fronted by an external proxy for TLS (Caddy itself stays on `:80` behind it). The value is baked fronted by an external proxy for TLS (Caddy itself stays on `:80` behind it). The value is baked
into `version.json` at build time, so only builds made after it was added check for updates. into `version.json` at build time, so only builds made after it was added check for updates.
With `generatePatch` (default true), release builds now need **build-time network to With `generatePatch` (default true), release builds need **build-time network to
`miniscp.com/dl`** so electrobun can fetch the currently-published version to diff a `.patch` `miniscp.com/dl`** so Hutch can fetch the currently-published version to diff a `.patch` (a
(the first build after enabling it has nothing to diff and just ships the full artifacts). target with no published feed yet — currently mac and Linux — just ships the full artifacts).
## Auto-updater (app side) ## Auto-updater (app side)
@@ -128,16 +152,21 @@ Wired on top of the download host above. See the "Auto-updater" section in
`Updater.onStatusChange` streams `download-progress` (0–100) → `updateStatus{downloading}`. `Updater.onStatusChange` streams `download-progress` (0–100) → `updateStatus{downloading}`.
`checkForUpdate` self-guards the `dev` channel, so `bun run dev` never shows the pill. `checkForUpdate` self-guards the `dev` channel, so `bun run dev` never shows the pill.
- **RPC** ([src/shared/rpc.ts](../src/shared/rpc.ts)): requests `checkForUpdate` / `downloadUpdate` / - **RPC** ([src/shared/rpc.ts](../src/shared/rpc.ts)): requests `checkForUpdate` / `downloadUpdate` /
`applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`); `applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`;
push message `updateStatus`. `applyUpdate` goes through `events.applyUpdate` in index.ts); push message `updateStatus`.
- **`applyUpdate()` asks the before-quit guard first (2.x).** With transfers running or dirty
editors the guard vetoes and shows its Quit Anyway / Cancel box. `events.applyUpdate` marks the
quit as update-initiated, so Quit Anyway re-runs `Updater.applyUpdate()` (a plain `Utils.quit()`
would drop the downloaded update); Cancel leaves the pill at "Restart to apply".
- **UI**: `UpdaterService` ([ui/src/app/core/updater.service.ts](../ui/src/app/core/updater.service.ts)) holds signals - **UI**: `UpdaterService` ([ui/src/app/core/updater.service.ts](../ui/src/app/core/updater.service.ts)) holds signals
(`state`/`version`/`progress`); the tab-bar "update pill" ([tab-bar.ts](../ui/src/app/skins/shadlike/tab-bar/tab-bar.ts)) (`state`/`version`/`progress`); the tab-bar "update pill" ([tab-bar.ts](../ui/src/app/skins/shadlike/tab-bar/tab-bar.ts))
is one accent (`bg-primary`) control that announces → fills as a progress bar while downloading → is one accent (`bg-primary`) control that announces → fills as a progress bar while downloading →
"Restart to apply". `state` is `available → downloading → ready` (ready derived from the "Restart to apply". `state` is `available → downloading → ready` (ready derived from the
`downloadUpdate` response). App-menu "Check for Updates…" ([menu.ts](../src/bun/menu.ts)) routes a `downloadUpdate` response). App-menu "Check for Updates…" ([menu.ts](../src/bun/menu.ts)) routes a
`check-for-updates` menu action into `UpdaterService.check()`. `check-for-updates` menu action into `UpdaterService.check()`.
- **Unsigned is fine:** `applyUpdate()` strips the macOS quarantine xattr before relaunch, so the - **Unsigned was fine on 1.18.1:** `applyUpdate()` stripped the macOS quarantine xattr before
swapped bundle isn't Gatekeeper-blocked. relaunch. 2.x hands the swap to a native post-exit helper (transactional replace, restores the
old app on failure) — re-check Gatekeeper on the first unsigned 2.x mac update.
## Icons ## Icons
@@ -149,8 +178,13 @@ Rerun after touching either SVG; outputs are committed (build inputs).
## electrobun config notes ## electrobun config notes
`electrobun.config.ts`: app identity comes from `BRANDING`; `build.bun.external` `electrobun.config.ts`: app identity comes from `BRANDING`; **`build.mainProcess:
must keep `["cpu-features", "*.node"]` (ssh2's optional native accelerators — "bun"` is load-bearing** — 2.x defaults to Cottontail, which ignores the whole
bundling them breaks the build; runtime falls back to pure JS); `build.bun` block (verified: without the line, `bunx electrobun config
`build.copy` ships the Angular dist and tray assets into `views://`; --env=stable` resolves `mainProcess: "cottontail"` with no externals);
`bundleCEF: false` everywhere (system WebKit / WebView2). `build.bun.external` must keep `["cpu-features", "*.node"]` (ssh2's optional
native accelerators — bundling them breaks the build; runtime falls back to pure
JS; a 2.x stable bundle keeps both as runtime `require`s); `build.copy` ships the
Angular dist and tray assets into `views://`; `bundleCEF: false` everywhere
(system WebKit / WebView2). `bunx electrobun config --env=stable` prints the
merged config Hutch will build with.
+80 -38
View File
@@ -87,6 +87,8 @@ touching the adjacent code.
`webviewGetPageZoom` returns a hardcoded `1.0` from `.rodata`, so the failure `webviewGetPageZoom` returns a hardcoded `1.0` from `.rodata`, so the failure
is unobservable through the API — set 2.0, read back "1.0 = success". The is unobservable through the API — set 2.0, read back "1.0 = success". The
JSDoc's "(WebKit only)" means macOS WKWebView. This is why the scale is CSS. JSDoc's "(WebKit only)" means macOS WKWebView. This is why the scale is CSS.
(Disassembled on 1.18.1; 2.0.2's zoom fixes are CEF-only — re-check the
native Linux wrapper before trusting it.)
- **Everything the UI sizes with must be rem.** The scale works by setting the - **Everything the UI sizes with must be rem.** The scale works by setting the
root font-size, so a px value freezes while everything around it grows. The root font-size, so a px value freezes while everything around it grows. The
px reservoirs were the panel-chrome tokens (`--row-h` etc.) and 93 px reservoirs were the panel-chrome tokens (`--row-h` etc.) and 93
@@ -101,25 +103,39 @@ touching the adjacent code.
## Electrobun ## Electrobun
- **The prebuilt CLI binary ships with a broken ad-hoc signature**, so macOS - **The `electrobun` npm package (2.x) is a bootstrap whose every export is a
SIGKILLs it at exec and `bun run start` prints nothing and exits **0** — throwing stub** ("Electrobun 2.x APIs come from the Hutch devkit, not
`bin/electrobun.cjs` spawns the real binary and forwards node_modules"). Every `electrobun/*` import must resolve through the
`process.exit(code || 0)`, and a signal death has `code === null`. The binary projected `.hutch/devkit/`: the root tsconfig extends
is a Bun single-file executable whose payload is appended *after* the linker `.hutch/devkit/tsconfig.json`, and `ui/tsconfig.json` maps `electrobun/view`
signed it, so `codesign -v` reports "code or signature have been modified"; to `../.hutch/devkit/api/browser/index.ts` (the exact export-map target). Let
the arm64 and x64 CLI tarballs are both affected upstream (confirmed by an import fall through to `node_modules/electrobun` and the webview throws at
hashing a fresh download against the shipped copy — it is not local load. tsc and the UI build fail on unresolved `electrobun/*` imports until
corruption, and re-installing does not help). `scripts/fix-electrobun-cli.ts` `bunx electrobun prepare` has run (postinstall and every dev/build script do
re-seals it with `codesign --force --sign -`. It runs from `postinstall` it). Never point anything into `~/.hutch` — that store is Hutch-owned.
**and** ahead of every electrobun invocation, because the wrapper downloads - **`build.mainProcess` defaults to Cottontail in 2.x**, and Cottontail ignores
the binary **lazily on first use**, not at install time — postinstall alone the whole `build.bun` block — the ssh2 externals included, silently (verified
would no-op on a fresh checkout. It also fixes `.cache/electrobun`, which the with `bunx electrobun config --env=stable`). `mainProcess: "bun"` in
wrapper restores `bin/` from. Never read a silent `exit 0` from the `electrobun.config.ts` is load-bearing.
electrobun CLI as "nothing happened": run `codesign -v` on it first. - **The browser SDK is raw TS compiled inside the Angular program**, so the UI's
- **Window `close` is not cancellable** (response type `{}`; only the strictness flags apply to upstream code: 2.0.2's `createTransport().send`
app-level `before-quit` supports `event.response = {allow:false}`). All returns on one path only, which is why `ui/tsconfig.json` has
unsaved-state guards must either intercept app-controlled close paths `noImplicitReturns: false`. Its `import "./global.d.ts"` also prints "File
(⌘W, buttons) or survive the close (editor draft mirroring). '…/global.d.ts' not found in TypeScript compilation" on every build — benign,
and adding the file to the program does not silence it.
- **Window `close` is not cancellable, but 2.x added `will-close`.** The `close`
event's response type is `{}`; `will-close` (fired by the titlebar/traffic-light
close and `win.requestClose()`) honors `event.response = {allow:false}`, and
`win.close()` skips it. The editor close safety predates this: all
unsaved-state guards still intercept app-controlled close paths (⌘W, buttons)
or survive the close (editor draft mirroring) — a `will-close` prompt is a
possible follow-up.
- **`Updater.applyUpdate()` asks the `before-quit` handlers first (2.x)**, so the
transfers/unsaved guard can veto an update restart. Its "Quit Anyway" must
re-run `Updater.applyUpdate()` (`events.applyUpdate` flags the quit as
update-initiated) — a plain `Utils.quit()` there quits without installing.
`Utils.quit()` runs the same approval, which is why the guard checks
`quitConfirmed` first.
- **⌘W in any secondary window arrives as the MAIN window's `close-tab` - **⌘W in any secondary window arrives as the MAIN window's `close-tab`
menu action** (macOS: the app menu intercepts it before the webview sees menu action** (macOS: the app menu intercepts it before the webview sees
the key). bun must check the focused secondary window first — the Settings the key). bun must check the focused secondary window first — the Settings
@@ -127,8 +143,10 @@ touching the adjacent code.
check makes ⌘W close a main-window tab behind it. check makes ⌘W close a main-window tab behind it.
- **One rpc instance per window** — BrowserView binding steals the rpc's - **One rpc instance per window** — BrowserView binding steals the rpc's
transport; a shared instance silently sends to the wrong webview. transport; a shared instance silently sends to the wrong webview.
- **RPC before socket open is silently lost.** Everything gates on the - **RPC before socket open was silently lost on 1.18.1.** 2.x Electroview queues
socket-open promise (RpcService `ready`). sends while its `hostSocket` (v1: `bunSocket`) is CONNECTING and falls back to
the native bridge; RpcService still gates everything on the socket-open
promise (`ready`) as belt-and-braces.
- The emitter has no `off()`. Window event closures must not capture records - The emitter has no `off()`. Window event closures must not capture records
you want garbage collected. you want garbage collected.
- `electrobun build` defaults to the **dev** channel; release builds need - `electrobun build` defaults to the **dev** channel; release builds need
@@ -140,12 +158,13 @@ touching the adjacent code.
- `ApplicationMenu` is macOS-only in our usage; accelerators on Windows are - `ApplicationMenu` is macOS-only in our usage; accelerators on Windows are
single-character only. single-character only.
- **`ContextMenu.showContextMenu` is a silent no-op on Linux** (electrobun's own - **`ContextMenu.showContextMenu` is a silent no-op on Linux** (electrobun's own
`dist/api/bun/proc/linux.md` says so; the app also prints "Application menus are `.hutch/devkit/api/sdks/main/proc/linux.md` says so, still in 2.0.2; the app
not supported on Linux" at boot). Because the UI suppresses the webview's own also prints "Application menus are not supported on Linux" at boot). Because
right-click menu, that left EVERY panel context menu dead on Linux — no error, the UI suppresses the webview's own right-click menu, that left EVERY panel
nothing happens. The menu is plain HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on context menu dead on Linux — no error, nothing happens. The menu is plain
every platform; don't reintroduce the native one for "nativeness" on mac/win HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on every platform;
without keeping a Linux path. don't reintroduce the native one for "nativeness" on mac/win without keeping
a Linux path.
- **Window-open flash prevention is three-layer** — remove any one and the - **Window-open flash prevention is three-layer** — remove any one and the
white/inverted blink returns: (1) windows are created `hidden: true` and white/inverted blink returns: (1) windows are created `hidden: true` and
revealed by `showWhenReady` on the webview's first `dom-ready` (800 ms revealed by `showWhenReady` on the webview's first `dom-ready` (800 ms
@@ -161,7 +180,8 @@ touching the adjacent code.
URL, never hardcode `views://`. URL, never hardcode `views://`.
- Upstream Bun panic (`FFI_Callback_threadsafe_call` → `web_worker.spin`): - Upstream Bun panic (`FFI_Callback_threadsafe_call` → `web_worker.spin`):
rare segfault under long sessions; upstream electrobun×Bun issue, restart rare segfault under long sessions; upstream electrobun×Bun issue, restart
recovers, nothing app-side to fix. recovers, nothing app-side to fix. Seen on Bun 1.3.13 (Electrobun 1.18.1);
2.0.2 ships Bun 1.4.0 — unconfirmed whether that fixed it.
- **Preloads and frames are construction-time only.** The preload is a - **Preloads and frames are construction-time only.** The preload is a
persistent `WebKitUserScript` at DOCUMENT_START, frozen at webview persistent `WebKitUserScript` at DOCUMENT_START, frozen at webview
construction — `webview.loadURL()` re-runs the OLD one construction — `webview.loadURL()` re-runs the OLD one
@@ -171,13 +191,25 @@ touching the adjacent code.
correct live over RPC. Recreating the main window is not an escape hatch: correct live over RPC. Recreating the main window is not an escape hatch:
`exitOnLastWindowClosed`, push closures and the tray capture it, and there `exitOnLastWindowClosed`, push closures and the tray capture it, and there
is no `off()`. is no `off()`.
- **`trafficLightOffset` changed meaning in 2.x.** 1.18.1 nudged the buttons
by the offset but clamped them inside the stock titlebar, so `{12, 13}` really
meant "as low as the titlebar allows" (left 21 / top 18 on macOS 26). 2.x adds
the full offset to macOS's default position (9/9 in the 32pt Tahoe titlebar)
and grows the titlebar to fit — the same numbers dropped the lights 4pt.
`chromeOptions()` now passes `{12, 4}`: left 21 / top 13, the 14pt buttons
vertically centred in the 40px `h-10` header (1.18.1's clamped spot, top 18,
was 5pt low). The default shifts with macOS's titlebar metrics (28pt / 7
before macOS 26), so re-check after an OS release. `win.setWindowButtonPosition(x, top)` is the absolute
alternative (re-applied on resize/fullscreen) if per-version drift matters.
- **Drag regions ignore `preventDefault`.** The preload starts a window move - **Drag regions ignore `preventDefault`.** The preload starts a window move
from a bubble-phase `mousedown` on `document` and never reads from a bubble-phase `mousedown` on `document` and never reads
`defaultPrevented`; a `pointerdown` handler doesn't stop it either (the `defaultPrevented`; a `pointerdown` handler doesn't stop it either (the
compat `mousedown` still fires). `isAppRegionDrag` checks compat `mousedown` still fires). `isAppRegionDragTarget` lets a `no-drag`
`.electrobun-webkit-app-region-no-drag` first — that class on the element is region win — the `.electrobun-webkit-app-region-no-drag` class (or, since
the only reliable suppressor (`dist/api/bun/preload/dragRegions.ts`). 2.x, `app-region: no-drag` CSS) on the element is the only reliable
- **No resize, maximize-event or double-click-maximize primitives (1.18.1).** suppressor (`.hutch/devkit/api/preload/dragRegions.ts`; re-checked on 2.0.2).
- **No resize, maximize-event or double-click-maximize primitives (1.18.1, still
absent from 2.0.2's `BrowserWindow`).**
`titleBarStyle: "hidden"` drops the whole frame (Cocoa `Titled` — the only `titleBarStyle: "hidden"` drops the whole frame (Cocoa `Titled` — the only
way to remove the traffic lights; `gtk_window_set_decorated(false)` on way to remove the traffic lights; `gtk_window_set_decorated(false)` on
Linux) and with it the WM resize borders; nothing restores them (no Linux) and with it the WM resize borders; nothing restores them (no
@@ -204,12 +236,15 @@ touching the adjacent code.
- **The app cannot apply this to itself.** WebKit reads the variable when it - **The app cannot apply this to itself.** WebKit reads the variable when it
forks its web process, and **Bun's `process.env` writes do not propagate to forks its web process, and **Bun's `process.env` writes do not propagate to
child processes** (verified: a spawned child sees it unset). Setting it in child processes** (verified: a spawned child sees it unset). Setting it in
`src/bun/index.ts` — even in a module imported before `electrobun/bun` — `src/bun/index.ts` — even in a module imported before `electrobun/main` —
does nothing. It must be in the environment before the app process starts. does nothing. It must be in the environment before the app process starts.
- Hence every launch path injects it: `scripts/linux-gpu.ts` (`launchEnv()`) - Only the dev launch paths inject it: `scripts/linux-gpu.ts` (`launchEnv()`)
for `bun run dev` / `bun run start`, and the `miniSCP.sh` wrapper that for `bun run dev` / `bun run start`. **A new dev launch path must call
`release.ts` writes beside `bin/launcher` in the tarball. **A new launch `launchEnv()` or it will render black on NVIDIA.** The release is the
path must call `launchEnv()` or it will render black on NVIDIA.** official 2.x Setup installer now (the v1 portable tarball and its
`miniSCP.sh` wrapper are gone), so an installed build on NVIDIA needs the
user to launch it with `WEBKIT_DISABLE_DMABUF_RENDERER=1` (e.g. in the
desktop entry's `Exec`).
- Gated on `/proc/driver/nvidia` so Intel/AMD keep hardware compositing, and - Gated on `/proc/driver/nvidia` so Intel/AMD keep hardware compositing, and
an explicit `WEBKIT_DISABLE_DMABUF_RENDERER` from the user always wins. an explicit `WEBKIT_DISABLE_DMABUF_RENDERER` from the user always wins.
- A "does it boot?" log check does NOT catch this — grep for the GBM line, or - A "does it boot?" log check does NOT catch this — grep for the GBM line, or
@@ -296,7 +331,14 @@ touching the adjacent code.
breaks `ng`. breaks `ng`.
- ssh2's optional native deps must stay `external: ["cpu-features", "*.node"]` - ssh2's optional native deps must stay `external: ["cpu-features", "*.node"]`
in `electrobun.config.ts` — bundling them breaks the build; ssh2 falls back in `electrobun.config.ts` — bundling them breaks the build; ssh2 falls back
to pure JS at runtime. to pure JS at runtime. (Inert without `mainProcess: "bun"` — see Electrobun.)
- **2.x installers dropped the `stable-` prefix; the updater feed kept it.**
`macos-arm64-miniSCP.dmg` / `win-x64-miniSCP-Setup.zip` /
`linux-x64-miniSCP-Setup.tar.gz` next to `stable-<os>-<arch>-update.json`,
`.tar.zst` and `.patch`. Anything that globs `artifacts/stable-*` misses the
installers — release.ts collects by `<os>-<arch>-`, deploy.sh publishes
`artifacts/*` verbatim (release.ts empties `artifacts/` first), and the site
links the new names.
- `bun run --cwd ui build` (flag order matters; `bun --cwd ui run build` does - `bun run --cwd ui build` (flag order matters; `bun --cwd ui run build` does
not do what you want). not do what you want).
- `version.gen.ts` is gitignored and regenerated by postinstall + every build - `version.gen.ts` is gitignored and regenerated by postinstall + every build
+36 -27
View File
@@ -1,8 +1,9 @@
# Platform notes — macOS / Windows / Linux behavior matrix # Platform notes — macOS / Windows / Linux behavior matrix
macOS is the primary target; Windows 11+ x64 and Linux x64 are **beta** targets macOS (Apple Silicon) is the primary target; Windows 11+ x64 and Linux x64 are
(win-arm64 and linux-arm64 deferred). Every platform seam carries a **beta** targets (win-arm64 and linux-arm64 deferred). **Intel Macs were
`PORTABILITY:` comment at the branch site. dropped** with the move to Electrobun 2.x, which publishes no macOS x64 core.
Every platform seam carries a `PORTABILITY:` comment at the branch site.
## Seam inventory (where platform branches live) ## Seam inventory (where platform branches live)
@@ -81,7 +82,8 @@ ldd build/dev-linux-x64/*/bin/libNativeWrapper.so | grep 'not found'
`libasar.so => not found` in that output is **expected and harmless** — it ships `libasar.so => not found` in that output is **expected and harmless** — it ships
in the same `bin/` folder and resolves at load time; only an unresolved in the same `bin/` folder and resolves at load time; only an unresolved
`libayatana-appindicator3.so.1` is fatal. `libayatana-appindicator3.so.1` is fatal. (Hard link and path verified on
1.18.1 — re-check both on the first 2.x Linux build.)
**Tray visibility** is a separate matter from linking: GNOME 3.26+ hides the **Tray visibility** is a separate matter from linking: GNOME 3.26+ hides the
legacy tray, so the icon needs the legacy tray, so the icon needs the
@@ -92,23 +94,26 @@ either way — the tray is a convenience, and every action it offers exists in-a
## Linux known issues (upstream electrobun) ## Linux known issues (upstream electrobun)
- **No native menus of any kind.** Application menus and `showContextMenu` are - **No native menus of any kind.** Application menus and `showContextMenu` are
both no-ops (electrobun's `dist/api/bun/proc/linux.md`; the app logs both no-ops (electrobun's `.hutch/devkit/api/sdks/main/proc/linux.md`, still in
2.0.2; the app logs
"Application menus are not supported on Linux" at boot). The app menubar was "Application menus are not supported on Linux" at boot). The app menubar was
already macOS-only, and the panel context menus are now HTML on every platform, already macOS-only, and the panel context menus are now HTML on every platform,
so nothing is missing at the UI level — but do not add a feature that depends so nothing is missing at the UI level — but do not add a feature that depends
on a native menu. on a native menu.
- **NVIDIA renders black without the DMABUF workaround.** WebKitGTK can't - **NVIDIA renders black without the DMABUF workaround.** WebKitGTK can't
allocate GBM buffers on the proprietary driver, so the window paints nothing allocate GBM buffers on the proprietary driver, so the window paints nothing
while the log looks perfectly healthy. Every launch path sets while the log looks perfectly healthy. The dev launches
`WEBKIT_DISABLE_DMABUF_RENDERER=1` when `/proc/driver/nvidia` exists — (`scripts/linux-gpu.ts`) set `WEBKIT_DISABLE_DMABUF_RENDERER=1` when
`scripts/linux-gpu.ts` for dev, the `miniSCP.sh` wrapper in the tarball for `/proc/driver/nvidia` exists; an installed build (the official Setup
releases. The app can't do it itself (Bun's `process.env` writes don't reach installer) does not — launch it with that variable set (e.g. in the desktop
child processes); see [gotchas.md](gotchas.md#linux). entry's `Exec`). The app can't do it itself (Bun's `process.env` writes don't
reach child processes); see [gotchas.md](gotchas.md#linux).
- **Benign startup noise**: a deprecation warning pointing at - **Benign startup noise**: a deprecation warning pointing at
`libayatana-appindicator-glib`. Not fatal. `libayatana-appindicator-glib`. Not fatal.
- **Unsigned, unpackaged**: the release artifact is a portable tarball — no - **Unsigned; Electrobun's own installer**: the release artifact is
AppImage, `.deb`, `.desktop` entry or icon installation. Unpack and run `linux-x64-miniSCP-Setup.tar.gz` — a self-extracting `installer` + README
`miniSCP.sh` (which execs `bin/launcher`). (2.x does the desktop-entry packaging and ships an uninstaller); no AppImage
or `.deb`. Layout unverified until the first 2.x Linux build.
## First-Linux-boot smoke test ## First-Linux-boot smoke test
@@ -130,21 +135,24 @@ either way — the tray is a convenience, and every action it offers exists in-a
(temp download → save → auto-upload) with the inotify watcher. (temp download → save → auto-upload) with the inotify watcher.
6. Move to Trash lands the item in the desktop's trash; connections.json is 0600. 6. Move to Trash lands the item in the desktop's trash; connections.json is 0600.
7. `bun scripts/release.ts --target linux-x64` produces 7. `bun scripts/release.ts --target linux-x64` produces
`release/linux-x64/miniSCP-Build<N>-linux-x64.tar.gz`; unpack it elsewhere and `release/linux-x64/miniSCP-Build<N>-linux-x64-Setup.tar.gz`; unpack it
run `miniSCP.sh`. elsewhere, run its `installer`, then launch the installed app (on NVIDIA with
`WEBKIT_DISABLE_DMABUF_RENDERER=1`).
## Windows known issues (upstream electrobun, documented not fixed) ## Windows known issues (upstream electrobun, documented not fixed)
- **HiDPI blur** — WebView2 renders at 1x on scaled displays - **HiDPI blur** — WebView2 renders at 1x on scaled displays
(blackboardsh/electrobun#324, open). Most user-visible defect; wait for (blackboardsh/electrobun#324, seen on 1.18.1). Most user-visible defect; 2.x
upstream DPI awareness. lists per-monitor DPI setup among its Windows fixes — re-check on the first
2.x Windows build.
- **Notifications** are legacy balloon style (`Shell_NotifyIcon`), not - **Notifications** are legacy balloon style (`Shell_NotifyIcon`), not
Windows toasts. Windows toasts.
- **Unsigned binaries** — Setup.exe and the portable exe both trigger - **Unsigned binaries** — Setup.exe and the portable exe both trigger
SmartScreen / Mark-of-the-Web on first run. No Authenticode support in SmartScreen / Mark-of-the-Web on first run. No Authenticode support (2.x:
electrobun 1.18.1 (signtool post-hook or electrobun-builder are options "Windows release signing is not currently part of Hutch's packaging
later). Installer unpacks to `%LOCALAPPDATA%`, desktop shortcut only — no pipeline"; a signtool post-hook is an option later). Installer unpacks to
Start Menu entry or uninstaller. `%LOCALAPPDATA%`, desktop shortcut only; 2.x installer builds add an
uninstaller (`%LOCALAPPDATA%\<identifier>\<install-root>\uninstall.exe`).
- `hiddenInset` degrades to `hidden` on Windows — irrelevant here because - `hiddenInset` degrades to `hidden` on Windows — irrelevant here because
non-mac windows use the native titlebar. non-mac windows use the native titlebar.
- `findInPage` is a `window.find()` shim on WebView2 — irrelevant here, the - `findInPage` is a `window.find()` shim on WebView2 — irrelevant here, the
@@ -155,10 +163,11 @@ either way — the tray is a convenience, and every action it offers exists in-a
Code paths written blind (no Windows box was available); verify in order: Code paths written blind (no Windows box was available); verify in order:
1. Build: `bun scripts/release.ts --target win-x64` on the Windows machine — 1. Build: `bun scripts/release.ts --target win-x64` on the Windows machine —
core binaries download from GitHub releases on first run; Setup.zip AND Hutch, the core and the pinned Bun download into `~/.hutch` on first run;
the portable zip appear under `release/win-x64/` (the portable step warns Setup.zip AND the portable zip appear under `release/win-x64/` (the portable
instead of failing if the assembled-app folder layout differs — fix the step warns instead of failing if the assembled-app folder layout differs —
`appDir` path in release.ts if so). fix the `appDir` path in release.ts if so). A v1 0.1.78 install must pick up
the published 2.x build through its updater (the bridge release).
2. App boots, tray icon renders colored, window has a native titlebar. 2. App boots, tray icon renders colored, window has a native titlebar.
3. Drives dropdown in each panel lists `C:\` (+ others); navigating to a 3. Drives dropdown in each panel lists `C:\` (+ others); navigating to a
drive root hides the `'..'` row; the path bar shows consistent `\` paths. drive root hides the `'..'` row; the path bar shows consistent `\` paths.
@@ -175,5 +184,5 @@ Code paths written blind (no Windows box was available); verify in order:
user after saving a connection. user after saving a connection.
9. Editor/manager/Settings windows open, modal bounce works, Ctrl+, opens 9. Editor/manager/Settings windows open, modal bounce works, Ctrl+, opens
Settings and About shows from its About section. Settings and About shows from its About section.
10. Known-issue confirmation: HiDPI blur on a scaled display (expected until 10. Known-issue check: HiDPI blur on a scaled display (seen on 1.18.1 — 2.x's
upstream fixes it). per-monitor DPI setup may have fixed it).
+3
View File
@@ -16,6 +16,9 @@ export default {
baseUrl: "https://miniscp.com/dl", baseUrl: "https://miniscp.com/dl",
}, },
build: { build: {
// Load-bearing: 2.x defaults to Cottontail, which ignores the whole
// `bun` block below — the ssh2 externals included.
mainProcess: "bun",
bun: { bun: {
entrypoint: "src/bun/index.ts", entrypoint: "src/bun/index.ts",
// ssh2's optional native accelerators must never be bundled; its // ssh2's optional native accelerators must never be bundled; its
+5 -5
View File
@@ -7,16 +7,15 @@
"ui" "ui"
], ],
"scripts": { "scripts": {
"dev": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun scripts/dev.ts", "dev": "bun scripts/gen-version.ts && bunx electrobun prepare && bun scripts/dev.ts",
"dev:ui": "bun run --cwd ui start", "dev:ui": "bun run --cwd ui start",
"build:ui": "bun run --cwd ui build", "build:ui": "bun run --cwd ui build",
"build": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun run build:ui && bunx electrobun build", "build": "bun scripts/gen-version.ts && bunx electrobun prepare && bun run build:ui && bunx electrobun build",
"start": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun run build:ui && bun scripts/start.ts", "start": "bun scripts/gen-version.ts && bunx electrobun prepare && bun run build:ui && bun scripts/start.ts",
"postinstall": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts", "postinstall": "bun scripts/gen-version.ts && bunx electrobun prepare",
"release": "bun scripts/release.ts" "release": "bun scripts/release.ts"
}, },
"dependencies": { "dependencies": {
"electrobun": "1.18.1",
"ssh-config": "^5.2.0", "ssh-config": "^5.2.0",
"ssh2": "^1.17.0" "ssh2": "^1.17.0"
}, },
@@ -24,6 +23,7 @@
"@resvg/resvg-js": "^2.6.2", "@resvg/resvg-js": "^2.6.2",
"@types/bun": "latest", "@types/bun": "latest",
"@types/ssh2": "^1.15.5", "@types/ssh2": "^1.15.5",
"electrobun": "2.0.2",
"png-to-ico": "^3.0.2", "png-to-ico": "^3.0.2",
"typescript": "~5.9.3" "typescript": "~5.9.3"
}, },
-46
View File
@@ -1,46 +0,0 @@
// Re-signs electrobun's prebuilt CLI binary, which ships with a BROKEN ad-hoc
// signature: it is a Bun single-file executable whose payload is appended after
// the linker signed it, so `codesign -v` reports "code or signature have been
// modified" and the macOS kernel SIGKILLs it at exec. The failure is invisible:
// electrobun's bin/electrobun.cjs wrapper spawns the real binary and forwards
// `process.exit(code || 0)` — a signal death has code === null, so `bun run
// start` printed nothing and exited 0 while never launching the app.
// `codesign --force --sign -` re-seals it ad-hoc and it runs normally.
//
// The binary is downloaded LAZILY by that wrapper on first use, not by install,
// so this runs both from postinstall and ahead of every electrobun invocation.
// Idempotent, and a no-op off macOS.
import { existsSync } from "node:fs";
import path from "node:path";
const ROOT = path.join(import.meta.dir, "..");
/** true if `bin` has (or now has) a signature the kernel will accept. */
export function ensureSigned(bin: string): boolean {
if (process.platform !== "darwin" || !existsSync(bin)) return true;
const valid = () => Bun.spawnSync(["codesign", "-v", bin], { stderr: "ignore" }).success;
if (valid()) return true;
const signed = Bun.spawnSync(["codesign", "--force", "--sign", "-", bin], { stderr: "pipe" });
if (!signed.success || !valid()) {
console.warn(`[electrobun-cli] could not re-sign ${path.relative(ROOT, bin)} — the app will not launch`);
return false;
}
console.log(`[electrobun-cli] re-signed ${path.relative(ROOT, bin)} (upstream ad-hoc signature is invalid)`);
return true;
}
if (import.meta.main) {
const pkgDir = path.join(ROOT, "node_modules", "electrobun");
const bin = path.join(pkgDir, "bin", "electrobun");
// The wrapper downloads the CLI on first run and only then execs it, so one
// throwaway invocation leaves a downloaded (still unsigned) binary behind
// even though that exec is killed.
if (process.platform === "darwin" && existsSync(pkgDir) && !existsSync(bin)) {
Bun.spawnSync(["bunx", "electrobun", "--version"], { cwd: ROOT, stdout: "ignore", stderr: "ignore" });
}
// .cache is what the wrapper restores bin/ from, so both copies need fixing.
const ok = [bin, path.join(pkgDir, ".cache", "electrobun")].map(ensureSigned).every(Boolean);
if (!ok) process.exit(1);
}
+4 -3
View File
@@ -7,9 +7,10 @@
// It has to be in the environment BEFORE the app process starts: WebKit reads // It has to be in the environment BEFORE the app process starts: WebKit reads
// it when it forks its web process, and Bun's `process.env` writes do NOT // it when it forks its web process, and Bun's `process.env` writes do NOT
// propagate to children (verified — a child sees the variable unset), so the // propagate to children (verified — a child sees the variable unset), so the
// app cannot set this for itself from inside src/bun/. Every launch path has to // app cannot set this for itself from inside src/bun/. Only the dev launch
// inject it: the dev scripts here, and the wrapper script that release.ts ships // paths (`bun run dev` / `bun run start`) inject it; an installed build (the
// beside the packaged launcher. // official Linux Setup installer) needs the user to set
// WEBKIT_DISABLE_DMABUF_RENDERER=1 themselves on NVIDIA.
// //
// Scoped to NVIDIA so Intel/AMD keep hardware compositing, and an explicit // Scoped to NVIDIA so Intel/AMD keep hardware compositing, and an explicit
// value from the user's environment always wins. // value from the user's environment always wins.
+35 -94
View File
@@ -1,24 +1,21 @@
// Build one release artifact for one target, on the matching host: // Build one release artifact for one target, on the matching host:
// bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64] // bun scripts/release.ts [--target mac-arm64|win-x64|linux-x64]
// Default target = the host. electrobun cannot cross-compile OSes (its // Default target = the host. electrobun cannot cross-compile (Hutch builds for
// packaging needs host tools: codesign/hdiutil on mac, rcedit/PowerShell on // its host OS and arch only; packaging needs host tools: codesign/hdiutil on
// Windows), so win-x64 must run on a Windows machine. mac-x64 IS buildable on // mac, rcedit/PowerShell on Windows), so win-x64 must run on a Windows machine.
// an arm64 Mac: we fetch electrobun's x64 CLI binary and run it under // There is no mac-x64: Electrobun 2.x publishes no macOS x64 core at all.
// Rosetta 2 — it sees an x64 host, pulls the x64 core, and packages mac-x64. // Artifacts land in release/<target>/miniSCP-Build<N>-<target><ext>.
// Artifacts land in release/<target>/miniSCP-Build<N>-<target>.<ext>. import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
import { cpSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
import os from "node:os";
import path from "node:path"; import path from "node:path";
import { ensureSigned } from "./fix-electrobun-cli";
const ROOT = path.join(import.meta.dir, ".."); const ROOT = path.join(import.meta.dir, "..");
const TARGETS = ["mac-arm64", "mac-x64", "win-x64", "linux-x64"] as const; const TARGETS = ["mac-arm64", "win-x64", "linux-x64"] as const;
type Target = (typeof TARGETS)[number]; type Target = (typeof TARGETS)[number];
function hostTarget(): Target { function hostTarget(): Target {
if (process.platform === "win32") return "win-x64"; if (process.platform === "win32") return "win-x64";
if (process.platform === "linux") return "linux-x64"; if (process.platform === "linux") return "linux-x64";
return process.arch === "arm64" ? "mac-arm64" : "mac-x64"; return "mac-arm64";
} }
function fail(message: string): never { function fail(message: string): never {
@@ -39,8 +36,10 @@ function run(cmd: string[], opts: { cwd?: string } = {}): void {
// ---- target selection + host guards ---- // ---- target selection + host guards ----
const argIndex = process.argv.indexOf("--target"); const argIndex = process.argv.indexOf("--target");
const requested = (argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget()) as Target; const arg = argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget();
if (!TARGETS.includes(requested)) fail(`unknown target "${requested}" (${TARGETS.join(", ")})`); if (arg === "mac-x64") fail("mac-x64 was dropped — Electrobun 2.x publishes no macOS x64 core.");
if (!TARGETS.includes(arg as Target)) fail(`unknown target "${arg}" (${TARGETS.join(", ")})`);
const requested = arg as Target;
if (requested === "win-x64" && process.platform !== "win32") { if (requested === "win-x64" && process.platform !== "win32") {
fail("win-x64 must be built on a Windows machine/runner — electrobun cannot cross-compile."); fail("win-x64 must be built on a Windows machine/runner — electrobun cannot cross-compile.");
@@ -48,15 +47,14 @@ if (requested === "win-x64" && process.platform !== "win32") {
if (requested === "linux-x64" && process.platform !== "linux") { if (requested === "linux-x64" && process.platform !== "linux") {
fail("linux-x64 must be built on a Linux machine/runner — electrobun cannot cross-compile."); fail("linux-x64 must be built on a Linux machine/runner — electrobun cannot cross-compile.");
} }
if (requested.startsWith("mac") && process.platform !== "darwin") { if (requested === "mac-arm64" && (process.platform !== "darwin" || process.arch !== "arm64")) {
fail(`${requested} must be built on macOS.`); fail("mac-arm64 must be built on an Apple Silicon Mac (Electrobun 2.x has no macOS x64 build).");
} }
const viaRosetta = requested === "mac-x64" && process.arch === "arm64";
// ---- prepare: version, icons, UI bundle ---- // ---- prepare: version, devkit, icons, UI bundle ----
run(["bun", "scripts/gen-version.ts"]); run(["bun", "scripts/gen-version.ts"]);
run(["bun", "scripts/fix-electrobun-cli.ts"]); run(["bunx", "electrobun", "prepare"]);
run(["bun", "scripts/gen-icons.ts"]); run(["bun", "scripts/gen-icons.ts"]);
run(["bun", "run", "--cwd", "ui", "build"]); run(["bun", "run", "--cwd", "ui", "build"]);
@@ -64,104 +62,47 @@ const { BUILD } = await import("../src/shared/version.gen");
const { BRANDING } = await import("../src/shared/branding"); const { BRANDING } = await import("../src/shared/branding");
const buildName = `Build${BUILD.number}`; const buildName = `Build${BUILD.number}`;
// ---- electrobun build (native, or x64 CLI under Rosetta) ---- // ---- electrobun build ----
async function x64Cli(): Promise<string> { // Start from an empty artifacts/: site/deploy.sh publishes it verbatim (the
const pkg = await Bun.file(path.join(ROOT, "node_modules", "electrobun", "package.json")).json(); // updater contract), so an earlier build's files must not ride along.
const dir = path.join(os.homedir(), ".cache", "miniscp", `electrobun-cli-x64-${pkg.version}`); const artifactsDir = path.join(ROOT, "artifacts");
const bin = path.join(dir, "electrobun"); rmSync(artifactsDir, { recursive: true, force: true });
// Same broken upstream ad-hoc signature as the arm64 CLI (see run(["bunx", "electrobun", "build", "--env=stable"]);
// scripts/fix-electrobun-cli.ts) — unsigned it is SIGKILLed on exec.
if (existsSync(bin)) {
if (!ensureSigned(bin)) fail(`x64 CLI at ${bin} has an unusable code signature`);
return bin;
}
console.log(`[release] downloading electrobun x64 CLI ${pkg.version} (one-time)…`);
mkdirSync(dir, { recursive: true });
const url = `https://github.com/blackboardsh/electrobun/releases/download/v${pkg.version}/electrobun-cli-darwin-x64.tar.gz`;
const tarball = path.join(dir, "cli.tar.gz");
run(["curl", "-fsSL", "-o", tarball, url]);
run(["tar", "-xzf", tarball, "-C", dir]);
if (!existsSync(bin)) fail(`x64 CLI tarball did not contain "electrobun" (${dir})`);
run(["chmod", "+x", bin]);
if (!ensureSigned(bin)) fail(`x64 CLI at ${bin} has an unusable code signature`);
return bin;
}
if (viaRosetta) {
// x64 Mach-O runs under Rosetta transparently; it sees process.arch === x64
const rosetta = Bun.spawnSync(["arch", "-x86_64", "true"]);
if (!rosetta.success) {
fail("Rosetta 2 is not installed — run: softwareupdate --install-rosetta --agree-to-license");
}
run([await x64Cli(), "build", "--env=stable"]);
} else {
run(["bunx", "electrobun", "build", "--env=stable"]);
}
// ---- collect artifacts ---- // ---- collect artifacts ----
const [osName, archName] = const [osName, archName] =
requested === "win-x64" requested === "win-x64" ? ["win", "x64"] : requested === "linux-x64" ? ["linux", "x64"] : ["macos", "arm64"];
? ["win", "x64"] // 2.x installers carry no channel prefix (macos-arm64-miniSCP.dmg); only the
: requested === "linux-x64" // update feed (stable-<os>-<arch>-*) keeps it, for v1-compatible clients.
? ["linux", "x64"] const prefix = `${osName}-${archName}-`;
: ["macos", requested.slice(4)];
const prefix = `stable-${osName}-${archName}-`;
const artifactsDir = path.join(ROOT, "artifacts");
const outDir = path.join(ROOT, "release", requested); const outDir = path.join(ROOT, "release", requested);
mkdirSync(outDir, { recursive: true }); mkdirSync(outDir, { recursive: true });
const produced = readdirSync(artifactsDir).filter( const produced = readdirSync(artifactsDir).filter(
(f) => f.startsWith(prefix) && f.includes(BRANDING.appName), (f) => f.startsWith(prefix) && f.includes(BRANDING.appName),
); );
if (!produced.length) fail(`no ${prefix}* artifacts found in artifacts/ — did the build run?`); if (!produced.length) fail(`no ${prefix}* installer found in artifacts/ — did the build run?`);
const out: string[] = []; const out: string[] = [];
for (const file of produced) { for (const file of produced) {
const ext = file.endsWith(".dmg") ? ".dmg" : file.endsWith("-Setup.zip") ? "-Setup.zip" : null; const ext = [".dmg", "-Setup.zip", "-Setup.tar.gz"].find((e) => file.endsWith(e));
if (!ext) continue; // skip the raw .app.tar.zst + update.json if (!ext) continue;
const name = `${BRANDING.appName}-${buildName}-${requested}${ext}`; const name = `${BRANDING.appName}-${buildName}-${requested}${ext}`;
cpSync(path.join(artifactsDir, file), path.join(outDir, name)); cpSync(path.join(artifactsDir, file), path.join(outDir, name));
out.push(name); out.push(name);
} }
// Windows / Linux: package the assembled app folder directly. On Windows that // Windows: also zip the assembled app folder — the portable build (unzip
// complements the Setup.exe installer (portable = unzip anywhere and run); on // anywhere and run) next to the Setup installer. Best-effort: the folder
// Linux it is the ONLY artifact, since electrobun emits just the .tar.zst // layout is confirmed by the first real 2.x Windows build.
// updater blob there — no installer, AppImage or .deb. Best-effort: the folder if (requested === "win-x64") {
// layout is confirmed by the first real build on each OS. const appDir = path.join(ROOT, "build", "stable-win-x64", BRANDING.appName);
if (requested === "win-x64" || requested === "linux-x64") {
const appDir = path.join(ROOT, "build", `stable-${osName}-${archName}`, BRANDING.appName);
if (existsSync(appDir)) { if (existsSync(appDir)) {
if (requested === "win-x64") {
const portable = path.join(outDir, `${BRANDING.appName}-${buildName}-win-x64-portable.zip`); const portable = path.join(outDir, `${BRANDING.appName}-${buildName}-win-x64-portable.zip`);
run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${portable}'`]); run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${portable}'`]);
out.push(path.basename(portable)); out.push(path.basename(portable));
} else {
// Ship a wrapper beside the launcher: the packaged app never goes
// through scripts/start.ts, and the NVIDIA/DMABUF workaround has to be
// in the environment BEFORE the process starts (see scripts/linux-gpu.ts).
const wrapper = path.join(appDir, `${BRANDING.appName}.sh`);
writeFileSync(
wrapper,
[
"#!/bin/sh",
"# WebKitGTK's DMABUF renderer cannot allocate GBM buffers on the NVIDIA",
"# proprietary driver — the window renders black. Disable it there only.",
'if [ -z "$WEBKIT_DISABLE_DMABUF_RENDERER" ] && [ -e /proc/driver/nvidia ]; then',
" export WEBKIT_DISABLE_DMABUF_RENDERER=1",
"fi",
'exec "$(dirname "$(readlink -f "$0")")/bin/launcher" "$@"',
"",
].join("\n"),
{ mode: 0o755 },
);
const tarball = path.join(outDir, `${BRANDING.appName}-${buildName}-linux-x64.tar.gz`);
// -C the parent so the archive unpacks into one named folder
run(["tar", "-czf", tarball, "-C", path.dirname(appDir), path.basename(appDir)]);
out.push(path.basename(tarball));
}
} else { } else {
console.warn(`[release] WARN: portable skipped — app folder not found at ${appDir}`); console.warn(`[release] WARN: portable skipped — app folder not found at ${appDir}`);
} }
+1 -1
View File
@@ -19,7 +19,7 @@
header @feed Cache-Control "no-cache" header @feed Cache-Control "no-cache"
# Installers/payloads keep stable names but get overwritten each release. # Installers/payloads keep stable names but get overwritten each release.
@pkg path *.dmg *.zip *.tar.zst @pkg path *.dmg *.zip *.tar.zst *.tar.gz
header @pkg Cache-Control "public, max-age=300" header @pkg Cache-Control "public, max-age=300"
# browse => directory listing; file_server serves range requests # browse => directory listing; file_server serves range requests
+7 -5
View File
@@ -42,7 +42,7 @@ electrobun can't cross-compile, so each OS/arch is built on its own machine. On
machine, after producing artifacts, drop them into the shared `dl/` folder: machine, after producing artifacts, drop them into the shared `dl/` folder:
```bash ```bash
bun scripts/release.ts # or --target mac-x64 / win-x64 bun scripts/release.ts # or --target win-x64 / linux-x64
site/deploy.sh # -> /srv/miniscp/dl site/deploy.sh # -> /srv/miniscp/dl
``` ```
@@ -56,14 +56,16 @@ rsync -av ./_dl/ user@host:/srv/miniscp/dl/
That's the whole workflow: **build, drop the artifacts, done.** Installers keep stable names and That's the whole workflow: **build, drop the artifacts, done.** Installers keep stable names and
overwrite in place, so the download links on the page never change. overwrite in place, so the download links on the page never change.
> **Don't delete old `*.patch` files.** They're the incremental-update trail for the (upcoming) > **Don't delete old `*.patch` files.** They're the incremental-update trail for the
> built-in updater — removing them forces older installs to re-download the full app. > built-in updater — removing them forces older installs to re-download the full app.
## Notes ## Notes
- Downloads live under `/dl/` and are browsable at `http://<host>/dl/`. - Downloads live under `/dl/` and are browsable at `http://<host>/dl/`.
- The page links to fixed, stable filenames (`stable-macos-arm64-miniSCP.dmg`, …) so it stays - The page links to fixed, stable filenames (`macos-arm64-miniSCP.dmg`, `win-x64-miniSCP-Setup.zip`,
static and always points at the latest build. …) so it stays static and always points at the latest build. Electrobun 2.x names installers
without the `stable-` prefix; only the updater feed (`stable-<os>-<arch>-update.json`, `.tar.zst`,
`.patch`) keeps it. Installers left over under the old v1 `stable-…` names are stale — delete them.
- Builds are currently unsigned — the FAQ on the page covers the macOS `xattr` step and the - Builds are currently unsigned — the FAQ on the page covers the macOS `xattr` step and the
Windows SmartScreen prompt. Windows SmartScreen prompt.
- `/dl/` is also the intended `release.baseUrl` for the future electrobun auto-updater. - `/dl/` is also the electrobun auto-updater's `release.baseUrl`.
+9 -6
View File
@@ -3,7 +3,7 @@ set -euo pipefail
# Publish this machine's built artifacts into the served flat download folder. # Publish this machine's built artifacts into the served flat download folder.
# #
# Run from the repo root AFTER building for this host's target, e.g.: # Run from the repo root AFTER building for this host's target, e.g.:
# bun scripts/release.ts # or --target mac-x64 / win-x64 # bun scripts/release.ts # or --target win-x64 / linux-x64
# site/deploy.sh # site/deploy.sh
# #
# electrobun cannot cross-compile, so each OS/arch is built (and deployed) on its # electrobun cannot cross-compile, so each OS/arch is built (and deployed) on its
@@ -19,12 +19,15 @@ set -euo pipefail
dl="${MINISCP_DL_DIR:-/srv/miniscp/dl}" dl="${MINISCP_DL_DIR:-/srv/miniscp/dl}"
mkdir -p "$dl" mkdir -p "$dl"
# electrobun's flat artifacts (stable-<os>-<arch>-*): installers, update payloads, # electrobun's flat artifacts, verbatim — the updater contract, never rename them:
# update.json, and (once release.baseUrl is set) .patch files. # the update feed (stable-<os>-<arch>-update.json, .tar.zst, .patch) plus the
if compgen -G 'artifacts/stable-*' >/dev/null; then # installers, which 2.x names without the channel prefix (macos-arm64-miniSCP.dmg,
cp -f artifacts/stable-* "$dl"/ # win-x64-miniSCP-Setup.zip). release.ts empties artifacts/ first, so it holds
# exactly this build.
if compgen -G 'artifacts/*' >/dev/null; then
cp -f artifacts/* "$dl"/
else else
echo "deploy: WARN — no artifacts/stable-* found (did scripts/release.ts run?)" >&2 echo "deploy: WARN — artifacts/ is empty (did scripts/release.ts run?)" >&2
fi fi
# Windows portable zip is a release.ts extra (build-numbered) → give it a stable name. # Windows portable zip is a release.ts extra (build-numbered) → give it a stable name.
+4 -5
View File
@@ -215,9 +215,8 @@
<h2 id="download">Download</h2> <h2 id="download">Download</h2>
<p>Always the latest stable build.</p> <p>Always the latest stable build.</p>
<div class="downloads"> <div class="downloads">
<a class="dl" href="/dl/stable-macos-arm64-miniSCP.dmg">macOS<small>Apple Silicon</small></a> <a class="dl" href="/dl/macos-arm64-miniSCP.dmg">macOS<small>Apple Silicon</small></a>
<a class="dl" href="/dl/stable-macos-x64-miniSCP.dmg">macOS<small>Intel</small></a> <a class="dl" href="/dl/win-x64-miniSCP-Setup.zip">Windows<small>Installer</small></a>
<a class="dl" href="/dl/stable-win-x64-miniSCP-Setup.zip">Windows<small>Installer</small></a>
<a class="dl" href="/dl/stable-win-x64-miniSCP-portable.zip">Windows<small>Portable</small></a> <a class="dl" href="/dl/stable-win-x64-miniSCP-portable.zip">Windows<small>Portable</small></a>
</div> </div>
<p class="browse">Looking for everything? <a href="/dl/">browse all files</a>.</p> <p class="browse">Looking for everything? <a href="/dl/">browse all files</a>.</p>
@@ -226,8 +225,8 @@
<h2 id="faq">FAQ</h2> <h2 id="faq">FAQ</h2>
<dl class="faqtable"> <dl class="faqtable">
<dt>Which platforms are supported?</dt> <dt>Which platforms are supported?</dt>
<dd>macOS (Apple Silicon and Intel) and Windows 11+ (x64). macOS is the primary target; <dd>macOS (Apple Silicon) and Windows 11+ (x64). macOS is the primary target;
Windows is currently in beta.</dd> Windows is currently in beta. Intel Macs are not supported.</dd>
<dt>macOS says the app is "damaged and can't be opened".</dt> <dt>macOS says the app is "damaged and can't be opened".</dt>
<dd>The builds aren't code-signed yet, so macOS quarantines them on download. Clear the <dd>The builds aren't code-signed yet, so macOS quarantines them on download. Clear the
+1 -1
View File
@@ -1,4 +1,4 @@
import { Utils, type BrowserWindow } from "electrobun/bun"; import { Utils, type BrowserWindow } from "electrobun/main";
import { BRANDING } from "../shared/branding"; import { BRANDING } from "../shared/branding";
import type { EditorOpenContext } from "../shared/rpc"; import type { EditorOpenContext } from "../shared/rpc";
import type { AppSettings } from "../shared/types"; import type { AppSettings } from "../shared/types";
+19 -2
View File
@@ -1,4 +1,4 @@
import Electrobun, { ApplicationMenu, BrowserWindow, Updater, Utils } from "electrobun/bun"; import Electrobun, { ApplicationMenu, BrowserWindow, Updater, Utils } from "electrobun/main";
import { BRANDING } from "../shared/branding"; import { BRANDING } from "../shared/branding";
import type { EditorOpenContext } from "../shared/rpc"; import type { EditorOpenContext } from "../shared/rpc";
import { effectiveConcurrency, type AppSettings } from "../shared/types"; import { effectiveConcurrency, type AppSettings } from "../shared/types";
@@ -55,6 +55,7 @@ const events = {
openConnectionManager: () => {}, openConnectionManager: () => {},
openSettings: () => {}, openSettings: () => {},
showAbout: () => {}, showAbout: () => {},
applyUpdate: async () => {},
}; };
const rpc = createAppRPC({ registry, engine, store, settings, edits, watcher, events }); const rpc = createAppRPC({ registry, engine, store, settings, edits, watcher, events });
// late-bound like events: a watched local dir changed -> tell the main webview // late-bound like events: a watched local dir changed -> tell the main webview
@@ -211,11 +212,24 @@ engine.onUpdate = (snapshot) => {
// don't quit out from under running transfers or unsaved editors // don't quit out from under running transfers or unsaved editors
let quitConfirmed = false; let quitConfirmed = false;
// Updater.applyUpdate() asks this guard first (2.x), so remember the quit is
// update-initiated: "Quit Anyway" must re-run the apply, or a plain quit would
// drop the downloaded update.
let applyingUpdate = false;
events.applyUpdate = async () => {
applyingUpdate = true;
try {
await Updater.applyUpdate();
} finally {
applyingUpdate = false;
}
};
Electrobun.events.on("before-quit", (event) => { Electrobun.events.on("before-quit", (event) => {
const transfers = engine.activeJobCount(); const transfers = engine.activeJobCount();
const dirtyEditors = editors.dirtyCount(); const dirtyEditors = editors.dirtyCount();
if (quitConfirmed || (transfers === 0 && dirtyEditors === 0)) return; if (quitConfirmed || (transfers === 0 && dirtyEditors === 0)) return;
(event as { response: { allow: boolean } }).response = { allow: false }; (event as { response: { allow: boolean } }).response = { allow: false };
const updating = applyingUpdate; // read now: it clears once the vetoed apply returns
void (async () => { void (async () => {
const parts = [ const parts = [
transfers > 0 && `${transfers} transfer${transfers === 1 ? " is" : "s are"} still running`, transfers > 0 && `${transfers} transfer${transfers === 1 ? " is" : "s are"} still running`,
@@ -233,7 +247,10 @@ Electrobun.events.on("before-quit", (event) => {
}); });
if (response === 0) { if (response === 0) {
quitConfirmed = true; quitConfirmed = true;
Utils.quit(); // the trailing quit is a no-op once the apply is quitting, and still
// quits if it bails (no prepared download)
if (updating) void Updater.applyUpdate().finally(() => Utils.quit());
else Utils.quit();
} }
})(); })();
}); });
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BrowserWindow } from "electrobun/bun"; import type { BrowserWindow } from "electrobun/main";
import type { AppSettings } from "../shared/types"; import type { AppSettings } from "../shared/types";
import { createManagerRPC } from "./rpc-handlers"; import { createManagerRPC } from "./rpc-handlers";
import type { ConnectionStore } from "./store/connections"; import type { ConnectionStore } from "./store/connections";
+1 -1
View File
@@ -1,4 +1,4 @@
import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/bun"; import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/main";
import { BRANDING } from "../shared/branding"; import { BRANDING } from "../shared/branding";
import type { SavedConnection } from "../shared/types"; import type { SavedConnection } from "../shared/types";
import { t } from "./i18n"; import { t } from "./i18n";
+4 -2
View File
@@ -1,4 +1,4 @@
import { BrowserView, Updater, Utils } from "electrobun/bun"; import { BrowserView, Updater, Utils } from "electrobun/main";
import type { AppRPC, EditorOpenContext } from "../shared/rpc"; import type { AppRPC, EditorOpenContext } from "../shared/rpc";
import type { AppSettings, Result } from "../shared/types"; import type { AppSettings, Result } from "../shared/types";
import { readFileText, writeFileText, type EditManager } from "./edits"; import { readFileText, writeFileText, type EditManager } from "./edits";
@@ -33,6 +33,8 @@ export interface AppDeps {
openConnectionManager: () => void; openConnectionManager: () => void;
openSettings: () => void; openSettings: () => void;
showAbout: () => void; showAbout: () => void;
/** "Restart to apply" — lives next to the before-quit guard it must get past. */
applyUpdate: () => Promise<void>;
}; };
} }
@@ -352,7 +354,7 @@ export function createAppRPC({
applyUpdate: () => applyUpdate: () =>
wrap(async () => { wrap(async () => {
await Updater.applyUpdate(); // relaunches + quits; may never resolve await events.applyUpdate(); // relaunches + quits; may never resolve
return {}; return {};
}), }),
}, },
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BrowserWindow } from "electrobun/bun"; import type { BrowserWindow } from "electrobun/main";
import type { AppSettings } from "../shared/types"; import type { AppSettings } from "../shared/types";
import { t } from "./i18n"; import { t } from "./i18n";
import { createSettingsRPC } from "./rpc-handlers"; import { createSettingsRPC } from "./rpc-handlers";
+1 -1
View File
@@ -1,4 +1,4 @@
import { Tray, type BrowserWindow } from "electrobun/bun"; import { Tray, type BrowserWindow } from "electrobun/main";
/** /**
* Menu-bar / notification-area icon. Clicking it brings the app window to the * Menu-bar / notification-area icon. Clicking it brings the app window to the
+7 -2
View File
@@ -1,4 +1,4 @@
import { BrowserWindow } from "electrobun/bun"; import { BrowserWindow } from "electrobun/main";
import type { AppSettings } from "../shared/types"; import type { AppSettings } from "../shared/types";
/** /**
@@ -44,10 +44,15 @@ export function buildPreload(
* lights overlay it, which is what IS_MAC_CHROME reserves pl-24 for); other * lights overlay it, which is what IS_MAC_CHROME reserves pl-24 for); other
* platforms keep the native titlebar. Constructor-only: there is no API to * platforms keep the native titlebar. Constructor-only: there is no API to
* re-decorate a live window. * re-decorate a live window.
*
* electrobun 2.x adds trafficLightOffset to macOS's default button position
* (9/9 in the 32pt macOS 26 titlebar) and grows the titlebar to fit — 1.18.1
* clamped the shift inside the stock titlebar instead. {12, 4} puts the 14pt
* buttons at left 21 / top 13: vertically centred in the 40px h-10 header.
*/ */
export function chromeOptions() { export function chromeOptions() {
return process.platform === "darwin" return process.platform === "darwin"
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } } ? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 4 } }
: {}; : {};
} }
-3
View File
@@ -1,3 +0,0 @@
// electrobun ships raw .ts sources whose WGPU adapter imports "three" without
// bundled types; we never use that path, so give tsc an any-typed stand-in.
declare module "three";
+2 -1
View File
@@ -1,4 +1,5 @@
{ {
"extends": "./.hutch/devkit/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ES2022", "target": "ES2022",
"useDefineForClassFields": false, "useDefineForClassFields": false,
@@ -16,6 +17,6 @@
"emitDecoratorMetadata": false, "emitDecoratorMetadata": false,
"skipLibCheck": true "skipLibCheck": true
}, },
"include": ["src"], "include": ["src", "electrobun.config.ts"],
"exclude": ["node_modules", "dist", "build", "../../package/dist"] "exclude": ["node_modules", "dist", "build", "../../package/dist"]
} }
+4 -4
View File
@@ -53,10 +53,10 @@ const trashKey = (): string => (IS_WINDOWS ? 'ctx.moveToRecycleBin' : 'ctx.moveT
/** /**
* Builds and opens the panel right-click menus. This used to be a native * Builds and opens the panel right-click menus. This used to be a native
* electrobun ContextMenu on the bun side, but `showContextMenu` is a documented * electrobun ContextMenu on the bun side, but `showContextMenu` is a documented
* no-op on Linux (electrobun's own dist/api/bun/proc/linux.md), which left the * no-op on Linux (electrobun's own .hutch/devkit/api/sdks/main/proc/linux.md),
* whole right-click surface dead there. The menu is plain HTML now — one code * which left the whole right-click surface dead there. The menu is plain HTML
* path on every platform — and still emits the exact same action strings, so * now — one code path on every platform — and still emits the exact same
* the dispatcher in app.ts is unchanged. * action strings, so the dispatcher in app.ts is unchanged.
*/ */
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class ContextMenuService { export class ContextMenuService {
+4 -3
View File
@@ -76,9 +76,10 @@ export class RpcService {
}); });
const electroview = new Electroview({ rpc: this.rpc }); const electroview = new Electroview({ rpc: this.rpc });
// Requests fired before the RPC WebSocket finishes connecting are lost, so // Gate everything below on the RPC WebSocket being open (services fire calls
// everything below gates on it being open (services fire calls at startup). // at startup). v1 silently lost pre-open sends; 2.x Electroview queues them
const socket = electroview.bunSocket; // and falls back to the native bridge, so this is now just belt-and-braces.
const socket = electroview.hostSocket;
if (socket && socket.readyState !== WebSocket.OPEN) { if (socket && socket.readyState !== WebSocket.OPEN) {
this.ready = new Promise((resolve) => { this.ready = new Promise((resolve) => {
socket.addEventListener('open', () => resolve(), { once: true }); socket.addEventListener('open', () => resolve(), { once: true });
+9 -3
View File
@@ -8,14 +8,20 @@
UI layer (services, platform, formatters). @core is an ALIAS rather than a UI layer (services, platform, formatters). @core is an ALIAS rather than a
relative path on purpose: skin components live at varying depths under relative path on purpose: skin components live at varying depths under
app/skins/<skin>/, and an alias keeps their imports identical wherever a app/skins/<skin>/, and an alias keeps their imports identical wherever a
component is moved to. */ component is moved to.
electrobun/view -> the SDK Hutch projects into .hutch/devkit (the npm
`electrobun` package only exports a throwing stub). Exact target from the
devkit's export map; `bunx electrobun prepare` must have run. */
"paths": { "paths": {
"@shared/*": ["../src/shared/*"], "@shared/*": ["../src/shared/*"],
"@core/*": ["./src/app/core/*"] "@core/*": ["./src/app/core/*"],
"electrobun/view": ["../.hutch/devkit/api/browser/index.ts"]
}, },
"noImplicitOverride": true, "noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true, "noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true, /* Off because the devkit's browser SDK is raw TS compiled inside this
program, and 2.0.2's transport send() returns on only one path. */
"noImplicitReturns": false,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"skipLibCheck": true, "skipLibCheck": true,
"isolatedModules": true, "isolatedModules": true,