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:
@@ -1,14 +1,16 @@
|
||||
# Release matrix — INERT until act_runner instances with these labels are
|
||||
# registered on git.teknes.es. Each OS builds its own artifacts (electrobun
|
||||
# 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:
|
||||
# macos — arm64 Mac: bun, Xcode CLT, Rosetta 2 (softwareupdate
|
||||
# --install-rosetta), network to github.com (electrobun downloads
|
||||
# its per-arch core binaries on first build)
|
||||
# windows — Windows 11 x64: bun, network to github.com; WebView2 runtime is
|
||||
# preinstalled on Win 11
|
||||
# macos — Apple Silicon Mac: bun, Xcode CLT, network to github.com and the
|
||||
# Electrobun/Hutch release hosts (`bun install` runs `electrobun
|
||||
# prepare`; the first build per Electrobun version fills ~/.hutch
|
||||
# with Hutch, the core, Cottontail and the pinned Bun, ~200 MB)
|
||||
# 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)).
|
||||
name: release
|
||||
@@ -26,7 +28,6 @@ jobs:
|
||||
fetch-depth: 0 # full history — the build number is the commit count
|
||||
- run: bun install --frozen-lockfile
|
||||
- run: bun scripts/release.ts --target mac-arm64
|
||||
- run: bun scripts/release.ts --target mac-x64
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: mac
|
||||
|
||||
@@ -8,3 +8,6 @@ ui/dist/
|
||||
ui/.angular/
|
||||
src/shared/version.gen.ts
|
||||
release/
|
||||
# Hutch-generated (electrobun 2.x): the projected SDK devkit and build scratch
|
||||
.hutch/
|
||||
.cottontail-tmp/
|
||||
|
||||
@@ -53,7 +53,7 @@ Dense reference material lives in [docs/](docs/): [architecture](docs/architectu
|
||||
| Layer | Choice |
|
||||
| ---------- | ------------------------------------------------------------- |
|
||||
| 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/` |
|
||||
| Styling | Tailwind CSS v4, Oxide-derived tokens (no component library) |
|
||||
| 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).
|
||||
- No Node install required — a preload shim (`scripts/node-version-shim.ts`)
|
||||
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
|
||||
|
||||
@@ -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`).
|
||||
|
||||
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
|
||||
bun run release -- --target mac-arm64 # on the Mac (native)
|
||||
bun run release -- --target mac-x64 # on the same arm64 Mac, via Rosetta 2
|
||||
bun run release -- --target mac-arm64 # on an Apple Silicon Mac
|
||||
bun run release -- --target win-x64 # on a Windows 11 x64 machine
|
||||
bun run release -- --target linux-x64 # on a Linux x64 machine
|
||||
```
|
||||
|
||||
Artifacts land in `release/<target>/miniSCP-Build<n>-<target>.*` — a `.dmg`
|
||||
per mac arch; on Windows a `-Setup.zip` installer **and** a `-portable.zip`
|
||||
(unzip anywhere and run); on Linux a portable `.tar.gz` (unpack and run
|
||||
`miniSCP.sh`, which sets the NVIDIA workaround then execs `bin/launcher`).
|
||||
The mac-x64 build fetches electrobun's x64 CLI once
|
||||
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
|
||||
on the Mac; on Windows a `-Setup.zip` installer **and** a `-portable.zip`
|
||||
(unzip anywhere and run); on Linux Electrobun's `-Setup.tar.gz` installer.
|
||||
There is no Intel Mac build: Electrobun 2.x publishes no macOS x64 core.
|
||||
CI: `.gitea/workflows/release.yml` runs the
|
||||
same script on a macos + windows runner matrix once act_runners are
|
||||
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
|
||||
`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).
|
||||
|
||||
## Rebranding
|
||||
@@ -233,11 +234,12 @@ electrobun APIs the app uses are implemented on Windows; known issues are
|
||||
upstream:
|
||||
|
||||
- **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.
|
||||
- **Unsigned binaries**: both the Setup.exe installer and the portable exe
|
||||
trigger SmartScreen/Mark-of-the-Web warnings on first run. No Start Menu
|
||||
entry or uninstaller (installer unpacks to `%LOCALAPPDATA%`).
|
||||
trigger SmartScreen/Mark-of-the-Web warnings on first run. The installer
|
||||
unpacks to `%LOCALAPPDATA%` and, since Electrobun 2.x, ships an uninstaller.
|
||||
- `connections.json` is protected with an `icacls` current-user ACL instead of
|
||||
POSIX 0600 (which is meaningless on Windows).
|
||||
|
||||
@@ -249,7 +251,8 @@ portable zip runs from any folder.
|
||||
|
||||
## 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
|
||||
[Prerequisites](#prerequisites) — electrobun links it for the tray, so without
|
||||
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.
|
||||
- **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.
|
||||
- **Unsigned and unpackaged**: no AppImage, `.deb`, desktop entry or icon
|
||||
installation — unpack the tarball and run `miniSCP.sh`.
|
||||
- **Unsigned**, and no AppImage or `.deb` — unpack the `-Setup.tar.gz` and run
|
||||
its `installer`.
|
||||
- **NVIDIA GPUs** need `WEBKIT_DISABLE_DMABUF_RENDERER=1` or the window renders
|
||||
black. Every launch path (dev scripts and the packaged `miniSCP.sh` wrapper)
|
||||
sets it automatically when an NVIDIA driver is detected — see
|
||||
[gotchas](docs/gotchas.md#linux) if you add a new way to launch the app.
|
||||
black. The dev scripts set it automatically when an NVIDIA driver is
|
||||
detected; an installed build needs it set by hand (e.g. in its desktop
|
||||
entry) — see [gotchas](docs/gotchas.md#linux).
|
||||
|
||||
Full dependency table, diagnostics and the first-boot smoke test:
|
||||
[platform notes](docs/platform-notes.md).
|
||||
@@ -273,17 +276,17 @@ Full dependency table, diagnostics and the first-boot smoke test:
|
||||
## Known issues
|
||||
|
||||
- **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
|
||||
bridge (`FFI_Callback_threadsafe_call` → `web_worker.spin` in the decoded
|
||||
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
|
||||
resolve when Electrobun bumps its pinned Bun (currently 1.3.13; 1.3.14+
|
||||
available). Bun's own crash reporter link is printed to the console on panic.
|
||||
restart recovers cleanly and in-flight work isn't corrupted. Electrobun 2.0.2
|
||||
pins Bun 1.4.0, which may have fixed it — not yet confirmed under load. Bun's
|
||||
own crash reporter link is printed to the console on panic.
|
||||
|
||||
## Not in v1
|
||||
|
||||
Drag-and-drop, file preview/edit, transfer resume, symlink following
|
||||
(symlinks are listed but skipped by transfers), OS keychain, tab persistence
|
||||
across restarts, Linux desktop integration (AppImage/.deb/.desktop entry —
|
||||
the release is a portable tarball).
|
||||
across restarts, Linux AppImage/.deb packages (the release is Electrobun's
|
||||
Setup installer).
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"": {
|
||||
"name": "bunscp",
|
||||
"dependencies": {
|
||||
"electrobun": "1.18.1",
|
||||
"ssh-config": "^5.2.0",
|
||||
"ssh2": "^1.17.0",
|
||||
},
|
||||
@@ -13,6 +12,7 @@
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@types/bun": "latest",
|
||||
"@types/ssh2": "^1.15.5",
|
||||
"electrobun": "2.0.2",
|
||||
"png-to-ico": "^3.0.2",
|
||||
"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=="],
|
||||
|
||||
"@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/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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@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/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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -513,14 +507,10 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -639,14 +623,6 @@
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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-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=="],
|
||||
|
||||
"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-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-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
|
||||
|
||||
"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=="],
|
||||
@@ -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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
@@ -859,8 +829,6 @@
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"rcedit": ["rcedit@4.0.1", "", { "dependencies": { "cross-spawn-windows-exe": "^1.1.0" } }, "sha512-bZdaQi34krFWhrDn+O53ccBDw0MkAT2Vhu75SqhtvhQu4OPyFM4RoVheyYiVQYdjhUi6EJMVWQ0tR6bCIYVkUg=="],
|
||||
|
||||
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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/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=="],
|
||||
|
||||
"@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/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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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/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-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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"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=="],
|
||||
|
||||
"@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/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-pipeline/minipass/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
|
||||
|
||||
+26
-14
@@ -6,15 +6,21 @@ Dense reference for development and debugging. Companion files:
|
||||
|
||||
## Process model
|
||||
|
||||
- **Bun main process** (`src/bun/`): electrobun runs it inside a WebWorker of
|
||||
its native launcher. Owns every privileged capability: filesystem, SSH,
|
||||
window management, native menus/dialogs/tray, the connection/settings stores.
|
||||
- **Bun main process** (`src/bun/`): the native launcher runs electrobun's
|
||||
`main.js` on the Bun the 2.x devkit pins (1.4.0, shipped in the bundle —
|
||||
`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 /
|
||||
WebView2 on Windows / WebKitGTK on Linux. Zero privileged access — everything
|
||||
goes through RPC.
|
||||
- **RPC transport**: one WebSocket server in the bun process, multiplexed by
|
||||
`webviewId` (each webview auto-connects using natively injected globals).
|
||||
Requests are typed by the `AppRPC` schema in `src/shared/rpc.ts`.
|
||||
`webviewId` (each webview auto-connects using natively injected globals;
|
||||
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
|
||||
|
||||
@@ -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
|
||||
no-op functions, reassigned in `src/bun/index.ts` after everything exists:
|
||||
`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
|
||||
|
||||
@@ -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
|
||||
hosts only that tag). `chromeOptions()` is the ONLY place the native frame is
|
||||
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
|
||||
header via `IS_MAC_CHROME`); other platforms keep the native titlebar and a
|
||||
compact `h-9` non-drag header.
|
||||
`trafficLightOffset {x:12,y:4}` (2.x semantics — see gotchas; the UI renders an
|
||||
`h-10 pl-24` drag-region header via `IS_MAC_CHROME`); other platforms keep the
|
||||
native titlebar and a compact `h-9` non-drag header.
|
||||
|
||||
`WindowRegistry<Rec>` (same file): keyed window map with dedupe/activate,
|
||||
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
|
||||
(`readFile`) — `openEditor` just creates the window with ctx in the preload.
|
||||
|
||||
**Close safety** (electrobun window `close` is NOT cancellable — verified, no
|
||||
`allow` response like `before-quit` has):
|
||||
**Close safety** (designed when electrobun window `close` was NOT cancellable —
|
||||
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 →
|
||||
styled in-window Save/Don't Save/Cancel; the confirm path calls
|
||||
`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`)
|
||||
|
||||
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
|
||||
click-swallowing overlay (`managerState` message); main drops its keyboard
|
||||
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
|
||||
an effect that also writes signals re-triggers itself (this froze the whole
|
||||
app once), and signal coalescing drops progress snapshots. All requests are
|
||||
gated on the socket-open promise — calls fired during boot would otherwise
|
||||
be silently lost. `request` and `send` are ready-gated Proxies.
|
||||
gated on the socket-open promise (`electroview.hostSocket`) — on 1.18.1 calls
|
||||
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,
|
||||
entries, sort, cursor, selection, type-to-filter. `sorted` computed:
|
||||
filter → dirs-first sort (name/size/mtime numeric, kind/rights/owner/group
|
||||
|
||||
+113
-79
@@ -14,81 +14,101 @@ semver-shaped (`0.1.<n>`) for electrobun's config/updater; `BRANDING.buildLabel`
|
||||
|
||||
## Dev loop
|
||||
|
||||
- `bun run dev` — gen-version → scripts/dev.ts: starts `ng serve` (HMR) and
|
||||
`electrobun dev --watch`, with `DEV_SERVER_URL` pointing the webviews at the
|
||||
dev server (only honored on the dev channel and only if the server answers).
|
||||
- `bun run build` — gen-version → Angular build → `electrobun build`
|
||||
(**dev channel** — fine for boot checks, NOT a release).
|
||||
- `bun run dev` — gen-version → `electrobun prepare` → scripts/dev.ts: starts
|
||||
`ng serve` (HMR) and `electrobun dev --watch`, with `DEV_SERVER_URL` pointing
|
||||
the webviews at the dev server (only honored on the dev channel and only if
|
||||
the server answers).
|
||||
- `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/)
|
||||
fakes a Node version the Angular CLI accepts under Bun. Both preloads are
|
||||
load-bearing.
|
||||
- `scripts/fix-electrobun-cli.ts` re-signs electrobun's prebuilt CLI binary,
|
||||
whose upstream ad-hoc signature is invalid — macOS SIGKILLs it and the app
|
||||
silently never launches, with exit code 0 (see [gotchas.md](gotchas.md)). It
|
||||
runs from `postinstall` **and** at the start of `dev`/`start`/`build`/
|
||||
`release`, because the CLI is downloaded lazily on first use rather than at
|
||||
install time; when the binary is missing it provokes that download first, so
|
||||
a fresh checkout self-heals. Equally load-bearing — removing it means
|
||||
`bun run start` does nothing at all.
|
||||
- **Electrobun 2.x is driven by Hutch.** The `electrobun` devDependency (exact
|
||||
`2.0.2`) is only a bootstrap: its first command downloads the paired Hutch
|
||||
(0.27.1) from the Electrobun GitHub release, checks its SHA-256 against
|
||||
`hutch-artifacts.json`, caches it under `~/.hutch/npm/electrobun/<ver>/`, and
|
||||
forwards every `electrobun …` to `hutch electrobun …`. Hutch then fetches the
|
||||
Electrobun core, its build-time Cottontail and the pinned Bun 1.4.0 (the
|
||||
runtime the app ships) into `~/.hutch` (~200 MB on a fresh machine) and
|
||||
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
|
||||
|
||||
`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).
|
||||
Steps: gen-version → fix-electrobun-cli → gen-icons → Angular build →
|
||||
`electrobun build --env=stable` → copy/rename artifacts into
|
||||
Steps: gen-version → electrobun prepare → gen-icons → Angular build → empty
|
||||
`artifacts/` → `electrobun build --env=stable` → copy/rename the installers into
|
||||
`release/<target>/miniSCP-Build<n>-<target>.*`.
|
||||
|
||||
Facts the script encodes:
|
||||
|
||||
- **`--env=stable` is mandatory for releases** — the CLI defaults to the dev
|
||||
channel (different app name `miniSCP-dev`, no distributable artifacts).
|
||||
- **No OS cross-compile.** The CLI builds for its host (its `targets` config
|
||||
field is unread in 1.18.1); packaging needs host tools (codesign/hdiutil on
|
||||
macOS, rcedit/PowerShell on Windows). Hence one script run per artifact, on
|
||||
the matching machine; `win-x64` on a non-Windows host and `linux-x64` on a
|
||||
non-Linux host both error out.
|
||||
- **mac-x64 from the arm64 Mac (Rosetta)**: the script downloads electrobun's
|
||||
x64 CLI binary once (`electrobun-cli-darwin-x64.tar.gz` from the GitHub
|
||||
release matching the installed version, cached at
|
||||
`~/.cache/miniscp/electrobun-cli-x64-<ver>/`) and runs it directly —
|
||||
Rosetta translates it, the CLI sees `process.arch === "x64"`, downloads the
|
||||
`dist-macos-x64` core into the electrobun package dir on first use, and
|
||||
packages a genuine x64 app. Note: Bun's `fetch`→`Bun.write` streaming hung
|
||||
on the GitHub redirect chain; the script uses `curl -fsSL` deliberately.
|
||||
- **Both CLI binaries need re-signing.** The x64 CLI fetched for the Rosetta
|
||||
path carries the same broken upstream signature as the arm64 one, so
|
||||
`x64Cli()` re-signs it after download *and* on cache reuse; the native path
|
||||
is covered by the `fix-electrobun-cli.ts` step up front.
|
||||
- **Core binary downloads**: the installed npm package ships mac-arm64
|
||||
binaries only; other `dist-<os>-<arch>` cores are fetched from
|
||||
`github.com/blackboardsh/electrobun/releases` at build time — network to
|
||||
GitHub is a build-time requirement on every fresh machine/runner.
|
||||
- **Artifact shapes** (stable channel, in `artifacts/`): macOS →
|
||||
`stable-macos-<arch>-miniSCP.dmg` + `.app.tar.zst` + `update.json`
|
||||
(updater feed); Windows → `stable-win-x64-miniSCP-Setup.zip`
|
||||
(self-extracting installer → `%LOCALAPPDATA%`) plus the script's own
|
||||
**portable zip** made from the assembled app folder under
|
||||
`build/stable-win-x64/` (layout to confirm on the first real Windows build —
|
||||
the step warns instead of failing); Linux → only
|
||||
`stable-linux-x64-miniSCP.tar.zst` + `update.json`, i.e. the updater feed and
|
||||
nothing installable, so the script's **`.tar.gz` of the assembled app folder**
|
||||
under `build/stable-linux-x64/` IS the release artifact (same best-effort
|
||||
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).
|
||||
- **`--env=stable` is mandatory for releases** — `electrobun build` defaults to
|
||||
the dev channel (different app name `miniSCP-dev`, no distributable artifacts).
|
||||
- **No cross-compile, and no Intel Mac.** Hutch builds for its host OS *and*
|
||||
arch only; packaging needs host tools (codesign/hdiutil on macOS,
|
||||
rcedit/PowerShell on Windows). Hence one script run per artifact, on the
|
||||
matching machine; `win-x64` on a non-Windows host and `linux-x64` on a
|
||||
non-Linux host both error out. **Electrobun 2.x publishes no macOS x64 core
|
||||
(nor an x64 Hutch)**, so mac-x64 was dropped — the v1 trick of running the x64
|
||||
CLI under Rosetta has nothing left to run. `--target mac-x64`, and any build
|
||||
on an Intel Mac, fail with that message.
|
||||
- **Runtime downloads**: on first use per Electrobun version Hutch fetches the
|
||||
platform core from `github.com/blackboardsh/electrobun/releases`
|
||||
(SHA-256-checked against `electrobun-artifacts.json`), plus Cottontail and the
|
||||
pinned Bun, into `~/.hutch` — network is a build-time requirement on every
|
||||
fresh machine/runner.
|
||||
- **`artifacts/` is emptied before the build** — `site/deploy.sh` publishes it
|
||||
verbatim, so it must hold exactly this build (stale files, e.g. v1-named
|
||||
installers, would otherwise ship).
|
||||
- **Artifact shapes** (stable channel, in `artifacts/`): 2.x installers carry
|
||||
**no channel prefix**; only the updater feed keeps `stable-<os>-<arch>-` (the
|
||||
names v1.18.1+ clients request). macOS → `macos-arm64-miniSCP.dmg` +
|
||||
`stable-macos-arm64-miniSCP.app.tar.zst` + `stable-macos-arm64-update.json`
|
||||
(verified); Windows → `win-x64-miniSCP-Setup.zip` (setup exe + hidden payload)
|
||||
+ the `stable-win-x64-*` feed, plus the script's own **portable zip** of
|
||||
`build/stable-win-x64/miniSCP` (warns instead of failing if that folder is
|
||||
missing); Linux → `linux-x64-miniSCP-Setup.tar.gz` (a self-extracting
|
||||
`installer` + README) + the `stable-linux-x64-*` feed. The official Linux
|
||||
installer IS the release artifact — the v1 portable tarball and its
|
||||
`miniSCP.sh` NVIDIA wrapper are gone, so an installed build on NVIDIA needs
|
||||
`WEBKIT_DISABLE_DMABUF_RENDERER=1` from the user (see gotchas.md). The
|
||||
Windows/Linux names come from the 2.x docs — confirm on the first real 2.x
|
||||
build there. A build that can reach the previous release at
|
||||
`release.baseUrl` also emits `stable-<os>-<arch>-<previous-hash>.patch`.
|
||||
- **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`.
|
||||
- The script filters artifacts by app name, so stale artifacts from older
|
||||
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)
|
||||
|
||||
`.gitea/workflows/release.yml` — tag-triggered (`build-*`), macos + windows
|
||||
runner matrix, each running the same release script; inert until act_runners
|
||||
with those labels are registered. Needs `fetch-depth: 0` (the build number is
|
||||
the commit count). Runner prereqs are commented in the workflow file.
|
||||
`.gitea/workflows/release.yml` — tag-triggered (`build-*`), macos (mac-arm64) +
|
||||
windows (win-x64) runner matrix, each running the same release script; inert
|
||||
until act_runners with those labels are registered. Needs `fetch-depth: 0` (the
|
||||
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)
|
||||
|
||||
@@ -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/`.
|
||||
|
||||
- `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
|
||||
release. Logo/screenshot `<img>` slots are commented in, images live in `site/assets/`.
|
||||
stable filenames** (`/dl/macos-arm64-miniSCP.dmg`, `/dl/win-x64-miniSCP-Setup.zip`, the portable
|
||||
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
|
||||
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
|
||||
names overwritten each release). Landing page has **no** SPA fallback → unknown paths 404.
|
||||
(updater must see new versions), installers (`*.dmg *.zip *.tar.zst *.tar.gz`) `max-age=300`
|
||||
(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`.
|
||||
- **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-*`
|
||||
into `/dl/`; installers overwrite in place, `.patch` files accumulate (never delete them, or
|
||||
older installs lose their incremental-update trail). **Tradeoff:** only the latest full
|
||||
doubles as the updater `baseUrl`. Publishing = drop this machine's whole `artifacts/` into
|
||||
`/dl/`, unrenamed; installers overwrite in place, `.patch` files accumulate (never delete them,
|
||||
or older installs lose their incremental-update trail). **Tradeoff:** only the latest full
|
||||
installer is downloadable; old full versions are overwritten.
|
||||
- `site/deploy.sh` — run per build machine after `scripts/release.ts`; copies `artifacts/stable-*`
|
||||
and renames the build-numbered win portable zip to the stable `stable-win-x64-miniSCP-portable.zip`.
|
||||
`MINISCP_DL_DIR` overrides the target (stage locally, then rsync to the server).
|
||||
- `site/deploy.sh` — run per build machine after `scripts/release.ts`; copies `artifacts/*`
|
||||
verbatim and renames the build-numbered win portable zip to the stable
|
||||
`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
|
||||
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.
|
||||
With `generatePatch` (default true), release builds now need **build-time network to
|
||||
`miniscp.com/dl`** so electrobun can fetch the currently-published version to diff a `.patch`
|
||||
(the first build after enabling it has nothing to diff and just ships the full artifacts).
|
||||
With `generatePatch` (default true), release builds need **build-time network to
|
||||
`miniscp.com/dl`** so Hutch can fetch the currently-published version to diff a `.patch` (a
|
||||
target with no published feed yet — currently mac and Linux — just ships the full artifacts).
|
||||
|
||||
## 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}`.
|
||||
`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` /
|
||||
`applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`);
|
||||
push message `updateStatus`.
|
||||
`applyUpdate` (handlers in [rpc-handlers.ts](../src/bun/rpc-handlers.ts) call the electrobun `Updater`;
|
||||
`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
|
||||
(`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 →
|
||||
"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
|
||||
`check-for-updates` menu action into `UpdaterService.check()`.
|
||||
- **Unsigned is fine:** `applyUpdate()` strips the macOS quarantine xattr before relaunch, so the
|
||||
swapped bundle isn't Gatekeeper-blocked.
|
||||
- **Unsigned was fine on 1.18.1:** `applyUpdate()` stripped the macOS quarantine xattr before
|
||||
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
|
||||
|
||||
@@ -149,8 +178,13 @@ Rerun after touching either SVG; outputs are committed (build inputs).
|
||||
|
||||
## electrobun config notes
|
||||
|
||||
`electrobun.config.ts`: app identity comes from `BRANDING`; `build.bun.external`
|
||||
must keep `["cpu-features", "*.node"]` (ssh2's optional native accelerators —
|
||||
bundling them breaks the build; runtime falls back to pure JS);
|
||||
`build.copy` ships the Angular dist and tray assets into `views://`;
|
||||
`bundleCEF: false` everywhere (system WebKit / WebView2).
|
||||
`electrobun.config.ts`: app identity comes from `BRANDING`; **`build.mainProcess:
|
||||
"bun"` is load-bearing** — 2.x defaults to Cottontail, which ignores the whole
|
||||
`build.bun` block (verified: without the line, `bunx electrobun config
|
||||
--env=stable` resolves `mainProcess: "cottontail"` with no externals);
|
||||
`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
@@ -87,6 +87,8 @@ touching the adjacent code.
|
||||
`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
|
||||
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
|
||||
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
|
||||
@@ -101,25 +103,39 @@ touching the adjacent code.
|
||||
|
||||
## Electrobun
|
||||
|
||||
- **The prebuilt CLI binary ships with a broken ad-hoc signature**, so macOS
|
||||
SIGKILLs it at exec and `bun run start` prints nothing and exits **0** —
|
||||
`bin/electrobun.cjs` spawns the real binary and forwards
|
||||
`process.exit(code || 0)`, and a signal death has `code === null`. The binary
|
||||
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";
|
||||
the arm64 and x64 CLI tarballs are both affected upstream (confirmed by
|
||||
hashing a fresh download against the shipped copy — it is not local
|
||||
corruption, and re-installing does not help). `scripts/fix-electrobun-cli.ts`
|
||||
re-seals it with `codesign --force --sign -`. It runs from `postinstall`
|
||||
**and** ahead of every electrobun invocation, because the wrapper downloads
|
||||
the binary **lazily on first use**, not at install time — postinstall alone
|
||||
would no-op on a fresh checkout. It also fixes `.cache/electrobun`, which the
|
||||
wrapper restores `bin/` from. Never read a silent `exit 0` from the
|
||||
electrobun CLI as "nothing happened": run `codesign -v` on it first.
|
||||
- **Window `close` is not cancellable** (response type `{}`; only the
|
||||
app-level `before-quit` supports `event.response = {allow:false}`). All
|
||||
unsaved-state guards must either intercept app-controlled close paths
|
||||
(⌘W, buttons) or survive the close (editor draft mirroring).
|
||||
- **The `electrobun` npm package (2.x) is a bootstrap whose every export is a
|
||||
throwing stub** ("Electrobun 2.x APIs come from the Hutch devkit, not
|
||||
node_modules"). Every `electrobun/*` import must resolve through the
|
||||
projected `.hutch/devkit/`: the root tsconfig extends
|
||||
`.hutch/devkit/tsconfig.json`, and `ui/tsconfig.json` maps `electrobun/view`
|
||||
to `../.hutch/devkit/api/browser/index.ts` (the exact export-map target). Let
|
||||
an import fall through to `node_modules/electrobun` and the webview throws at
|
||||
load. tsc and the UI build fail on unresolved `electrobun/*` imports until
|
||||
`bunx electrobun prepare` has run (postinstall and every dev/build script do
|
||||
it). Never point anything into `~/.hutch` — that store is Hutch-owned.
|
||||
- **`build.mainProcess` defaults to Cottontail in 2.x**, and Cottontail ignores
|
||||
the whole `build.bun` block — the ssh2 externals included, silently (verified
|
||||
with `bunx electrobun config --env=stable`). `mainProcess: "bun"` in
|
||||
`electrobun.config.ts` is load-bearing.
|
||||
- **The browser SDK is raw TS compiled inside the Angular program**, so the UI's
|
||||
strictness flags apply to upstream code: 2.0.2's `createTransport().send`
|
||||
returns on one path only, which is why `ui/tsconfig.json` has
|
||||
`noImplicitReturns: false`. Its `import "./global.d.ts"` also prints "File
|
||||
'…/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`
|
||||
menu action** (macOS: the app menu intercepts it before the webview sees
|
||||
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.
|
||||
- **One rpc instance per window** — BrowserView binding steals the rpc's
|
||||
transport; a shared instance silently sends to the wrong webview.
|
||||
- **RPC before socket open is silently lost.** Everything gates on the
|
||||
socket-open promise (RpcService `ready`).
|
||||
- **RPC before socket open was silently lost on 1.18.1.** 2.x Electroview queues
|
||||
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
|
||||
you want garbage collected.
|
||||
- `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
|
||||
single-character only.
|
||||
- **`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
|
||||
not supported on Linux" at boot). Because the UI suppresses the webview's own
|
||||
right-click menu, that left EVERY panel context menu dead on Linux — no error,
|
||||
nothing happens. The menu is plain HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on
|
||||
every platform; don't reintroduce the native one for "nativeness" on mac/win
|
||||
without keeping a Linux path.
|
||||
`.hutch/devkit/api/sdks/main/proc/linux.md` says so, still in 2.0.2; the app
|
||||
also prints "Application menus are not supported on Linux" at boot). Because
|
||||
the UI suppresses the webview's own right-click menu, that left EVERY panel
|
||||
context menu dead on Linux — no error, nothing happens. The menu is plain
|
||||
HTML now (`ui/src/app/skins/shadlike/context-menu.ts`) on every platform;
|
||||
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
|
||||
white/inverted blink returns: (1) windows are created `hidden: true` and
|
||||
revealed by `showWhenReady` on the webview's first `dom-ready` (800 ms
|
||||
@@ -161,7 +180,8 @@ touching the adjacent code.
|
||||
URL, never hardcode `views://`.
|
||||
- Upstream Bun panic (`FFI_Callback_threadsafe_call` → `web_worker.spin`):
|
||||
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
|
||||
persistent `WebKitUserScript` at DOCUMENT_START, frozen at webview
|
||||
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:
|
||||
`exitOnLastWindowClosed`, push closures and the tray capture it, and there
|
||||
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
|
||||
from a bubble-phase `mousedown` on `document` and never reads
|
||||
`defaultPrevented`; a `pointerdown` handler doesn't stop it either (the
|
||||
compat `mousedown` still fires). `isAppRegionDrag` checks
|
||||
`.electrobun-webkit-app-region-no-drag` first — that class on the element is
|
||||
the only reliable suppressor (`dist/api/bun/preload/dragRegions.ts`).
|
||||
- **No resize, maximize-event or double-click-maximize primitives (1.18.1).**
|
||||
compat `mousedown` still fires). `isAppRegionDragTarget` lets a `no-drag`
|
||||
region win — the `.electrobun-webkit-app-region-no-drag` class (or, since
|
||||
2.x, `app-region: no-drag` CSS) on the element is the only reliable
|
||||
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
|
||||
way to remove the traffic lights; `gtk_window_set_decorated(false)` on
|
||||
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
|
||||
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
|
||||
`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.
|
||||
- Hence every launch path injects it: `scripts/linux-gpu.ts` (`launchEnv()`)
|
||||
for `bun run dev` / `bun run start`, and the `miniSCP.sh` wrapper that
|
||||
`release.ts` writes beside `bin/launcher` in the tarball. **A new launch
|
||||
path must call `launchEnv()` or it will render black on NVIDIA.**
|
||||
- Only the dev launch paths inject it: `scripts/linux-gpu.ts` (`launchEnv()`)
|
||||
for `bun run dev` / `bun run start`. **A new dev launch path must call
|
||||
`launchEnv()` or it will render black on NVIDIA.** The release is the
|
||||
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
|
||||
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
|
||||
@@ -296,7 +331,14 @@ touching the adjacent code.
|
||||
breaks `ng`.
|
||||
- ssh2's optional native deps must stay `external: ["cpu-features", "*.node"]`
|
||||
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
|
||||
not do what you want).
|
||||
- `version.gen.ts` is gitignored and regenerated by postinstall + every build
|
||||
|
||||
+36
-27
@@ -1,8 +1,9 @@
|
||||
# Platform notes — macOS / Windows / Linux behavior matrix
|
||||
|
||||
macOS is the primary target; Windows 11+ x64 and Linux x64 are **beta** targets
|
||||
(win-arm64 and linux-arm64 deferred). Every platform seam carries a
|
||||
`PORTABILITY:` comment at the branch site.
|
||||
macOS (Apple Silicon) is the primary target; Windows 11+ x64 and Linux x64 are
|
||||
**beta** targets (win-arm64 and linux-arm64 deferred). **Intel Macs were
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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)
|
||||
|
||||
- **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
|
||||
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
|
||||
on a native menu.
|
||||
- **NVIDIA renders black without the DMABUF workaround.** WebKitGTK can't
|
||||
allocate GBM buffers on the proprietary driver, so the window paints nothing
|
||||
while the log looks perfectly healthy. Every launch path sets
|
||||
`WEBKIT_DISABLE_DMABUF_RENDERER=1` when `/proc/driver/nvidia` exists —
|
||||
`scripts/linux-gpu.ts` for dev, the `miniSCP.sh` wrapper in the tarball for
|
||||
releases. The app can't do it itself (Bun's `process.env` writes don't reach
|
||||
child processes); see [gotchas.md](gotchas.md#linux).
|
||||
while the log looks perfectly healthy. The dev launches
|
||||
(`scripts/linux-gpu.ts`) set `WEBKIT_DISABLE_DMABUF_RENDERER=1` when
|
||||
`/proc/driver/nvidia` exists; an installed build (the official Setup
|
||||
installer) does not — launch it with that variable set (e.g. in the desktop
|
||||
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
|
||||
`libayatana-appindicator-glib`. Not fatal.
|
||||
- **Unsigned, unpackaged**: the release artifact is a portable tarball — no
|
||||
AppImage, `.deb`, `.desktop` entry or icon installation. Unpack and run
|
||||
`miniSCP.sh` (which execs `bin/launcher`).
|
||||
- **Unsigned; Electrobun's own installer**: the release artifact is
|
||||
`linux-x64-miniSCP-Setup.tar.gz` — a self-extracting `installer` + README
|
||||
(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
|
||||
|
||||
@@ -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.
|
||||
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
|
||||
`release/linux-x64/miniSCP-Build<N>-linux-x64.tar.gz`; unpack it elsewhere and
|
||||
run `miniSCP.sh`.
|
||||
`release/linux-x64/miniSCP-Build<N>-linux-x64-Setup.tar.gz`; unpack it
|
||||
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)
|
||||
|
||||
- **HiDPI blur** — WebView2 renders at 1x on scaled displays
|
||||
(blackboardsh/electrobun#324, open). Most user-visible defect; wait for
|
||||
upstream DPI awareness.
|
||||
(blackboardsh/electrobun#324, seen on 1.18.1). Most user-visible defect; 2.x
|
||||
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
|
||||
Windows toasts.
|
||||
- **Unsigned binaries** — Setup.exe and the portable exe both trigger
|
||||
SmartScreen / Mark-of-the-Web on first run. No Authenticode support in
|
||||
electrobun 1.18.1 (signtool post-hook or electrobun-builder are options
|
||||
later). Installer unpacks to `%LOCALAPPDATA%`, desktop shortcut only — no
|
||||
Start Menu entry or uninstaller.
|
||||
SmartScreen / Mark-of-the-Web on first run. No Authenticode support (2.x:
|
||||
"Windows release signing is not currently part of Hutch's packaging
|
||||
pipeline"; a signtool post-hook is an option later). Installer unpacks to
|
||||
`%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
|
||||
non-mac windows use the native titlebar.
|
||||
- `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:
|
||||
|
||||
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
|
||||
the portable zip appear under `release/win-x64/` (the portable step warns
|
||||
instead of failing if the assembled-app folder layout differs — fix the
|
||||
`appDir` path in release.ts if so).
|
||||
Hutch, the core and the pinned Bun download into `~/.hutch` on first run;
|
||||
Setup.zip AND the portable zip appear under `release/win-x64/` (the portable
|
||||
step warns instead of failing if the assembled-app folder layout differs —
|
||||
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.
|
||||
3. Drives dropdown in each panel lists `C:\` (+ others); navigating to a
|
||||
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.
|
||||
9. Editor/manager/Settings windows open, modal bounce works, Ctrl+, opens
|
||||
Settings and About shows from its About section.
|
||||
10. Known-issue confirmation: HiDPI blur on a scaled display (expected until
|
||||
upstream fixes it).
|
||||
10. Known-issue check: HiDPI blur on a scaled display (seen on 1.18.1 — 2.x's
|
||||
per-monitor DPI setup may have fixed it).
|
||||
|
||||
@@ -16,6 +16,9 @@ export default {
|
||||
baseUrl: "https://miniscp.com/dl",
|
||||
},
|
||||
build: {
|
||||
// Load-bearing: 2.x defaults to Cottontail, which ignores the whole
|
||||
// `bun` block below — the ssh2 externals included.
|
||||
mainProcess: "bun",
|
||||
bun: {
|
||||
entrypoint: "src/bun/index.ts",
|
||||
// ssh2's optional native accelerators must never be bundled; its
|
||||
|
||||
+5
-5
@@ -7,16 +7,15 @@
|
||||
"ui"
|
||||
],
|
||||
"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",
|
||||
"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",
|
||||
"start": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts && bun run build:ui && bun scripts/start.ts",
|
||||
"postinstall": "bun scripts/gen-version.ts && bun scripts/fix-electrobun-cli.ts",
|
||||
"build": "bun scripts/gen-version.ts && bunx electrobun prepare && bun run build:ui && bunx electrobun build",
|
||||
"start": "bun scripts/gen-version.ts && bunx electrobun prepare && bun run build:ui && bun scripts/start.ts",
|
||||
"postinstall": "bun scripts/gen-version.ts && bunx electrobun prepare",
|
||||
"release": "bun scripts/release.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"electrobun": "1.18.1",
|
||||
"ssh-config": "^5.2.0",
|
||||
"ssh2": "^1.17.0"
|
||||
},
|
||||
@@ -24,6 +23,7 @@
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@types/bun": "latest",
|
||||
"@types/ssh2": "^1.15.5",
|
||||
"electrobun": "2.0.2",
|
||||
"png-to-ico": "^3.0.2",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -7,9 +7,10 @@
|
||||
// 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
|
||||
// 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
|
||||
// inject it: the dev scripts here, and the wrapper script that release.ts ships
|
||||
// beside the packaged launcher.
|
||||
// app cannot set this for itself from inside src/bun/. Only the dev launch
|
||||
// paths (`bun run dev` / `bun run start`) inject it; an installed build (the
|
||||
// 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
|
||||
// value from the user's environment always wins.
|
||||
|
||||
+35
-94
@@ -1,24 +1,21 @@
|
||||
// Build one release artifact for one target, on the matching host:
|
||||
// bun scripts/release.ts [--target mac-arm64|mac-x64|win-x64|linux-x64]
|
||||
// Default target = the host. electrobun cannot cross-compile OSes (its
|
||||
// packaging needs host tools: codesign/hdiutil on mac, rcedit/PowerShell on
|
||||
// Windows), so win-x64 must run on a Windows machine. mac-x64 IS buildable on
|
||||
// an arm64 Mac: we fetch electrobun's x64 CLI binary and run it under
|
||||
// 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>.
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
||||
import os from "node:os";
|
||||
// bun scripts/release.ts [--target mac-arm64|win-x64|linux-x64]
|
||||
// Default target = the host. electrobun cannot cross-compile (Hutch builds for
|
||||
// its host OS and arch only; packaging needs host tools: codesign/hdiutil on
|
||||
// mac, rcedit/PowerShell on Windows), so win-x64 must run on a Windows machine.
|
||||
// There is no mac-x64: Electrobun 2.x publishes no macOS x64 core at all.
|
||||
// Artifacts land in release/<target>/miniSCP-Build<N>-<target><ext>.
|
||||
import { cpSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { ensureSigned } from "./fix-electrobun-cli";
|
||||
|
||||
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];
|
||||
|
||||
function hostTarget(): Target {
|
||||
if (process.platform === "win32") return "win-x64";
|
||||
if (process.platform === "linux") return "linux-x64";
|
||||
return process.arch === "arm64" ? "mac-arm64" : "mac-x64";
|
||||
return "mac-arm64";
|
||||
}
|
||||
|
||||
function fail(message: string): never {
|
||||
@@ -39,8 +36,10 @@ function run(cmd: string[], opts: { cwd?: string } = {}): void {
|
||||
// ---- target selection + host guards ----
|
||||
|
||||
const argIndex = process.argv.indexOf("--target");
|
||||
const requested = (argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget()) as Target;
|
||||
if (!TARGETS.includes(requested)) fail(`unknown target "${requested}" (${TARGETS.join(", ")})`);
|
||||
const arg = argIndex >= 0 ? process.argv[argIndex + 1] : hostTarget();
|
||||
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") {
|
||||
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") {
|
||||
fail("linux-x64 must be built on a Linux machine/runner — electrobun cannot cross-compile.");
|
||||
}
|
||||
if (requested.startsWith("mac") && process.platform !== "darwin") {
|
||||
fail(`${requested} must be built on macOS.`);
|
||||
if (requested === "mac-arm64" && (process.platform !== "darwin" || process.arch !== "arm64")) {
|
||||
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/fix-electrobun-cli.ts"]);
|
||||
run(["bunx", "electrobun", "prepare"]);
|
||||
run(["bun", "scripts/gen-icons.ts"]);
|
||||
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 buildName = `Build${BUILD.number}`;
|
||||
|
||||
// ---- electrobun build (native, or x64 CLI under Rosetta) ----
|
||||
// ---- electrobun build ----
|
||||
|
||||
async function x64Cli(): Promise<string> {
|
||||
const pkg = await Bun.file(path.join(ROOT, "node_modules", "electrobun", "package.json")).json();
|
||||
const dir = path.join(os.homedir(), ".cache", "miniscp", `electrobun-cli-x64-${pkg.version}`);
|
||||
const bin = path.join(dir, "electrobun");
|
||||
// Same broken upstream ad-hoc signature as the arm64 CLI (see
|
||||
// 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"]);
|
||||
}
|
||||
// Start from an empty artifacts/: site/deploy.sh publishes it verbatim (the
|
||||
// updater contract), so an earlier build's files must not ride along.
|
||||
const artifactsDir = path.join(ROOT, "artifacts");
|
||||
rmSync(artifactsDir, { recursive: true, force: true });
|
||||
run(["bunx", "electrobun", "build", "--env=stable"]);
|
||||
|
||||
// ---- collect artifacts ----
|
||||
|
||||
const [osName, archName] =
|
||||
requested === "win-x64"
|
||||
? ["win", "x64"]
|
||||
: requested === "linux-x64"
|
||||
? ["linux", "x64"]
|
||||
: ["macos", requested.slice(4)];
|
||||
const prefix = `stable-${osName}-${archName}-`;
|
||||
const artifactsDir = path.join(ROOT, "artifacts");
|
||||
requested === "win-x64" ? ["win", "x64"] : requested === "linux-x64" ? ["linux", "x64"] : ["macos", "arm64"];
|
||||
// 2.x installers carry no channel prefix (macos-arm64-miniSCP.dmg); only the
|
||||
// update feed (stable-<os>-<arch>-*) keeps it, for v1-compatible clients.
|
||||
const prefix = `${osName}-${archName}-`;
|
||||
const outDir = path.join(ROOT, "release", requested);
|
||||
mkdirSync(outDir, { recursive: true });
|
||||
|
||||
const produced = readdirSync(artifactsDir).filter(
|
||||
(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[] = [];
|
||||
for (const file of produced) {
|
||||
const ext = file.endsWith(".dmg") ? ".dmg" : file.endsWith("-Setup.zip") ? "-Setup.zip" : null;
|
||||
if (!ext) continue; // skip the raw .app.tar.zst + update.json
|
||||
const ext = [".dmg", "-Setup.zip", "-Setup.tar.gz"].find((e) => file.endsWith(e));
|
||||
if (!ext) continue;
|
||||
const name = `${BRANDING.appName}-${buildName}-${requested}${ext}`;
|
||||
cpSync(path.join(artifactsDir, file), path.join(outDir, name));
|
||||
out.push(name);
|
||||
}
|
||||
|
||||
// Windows / Linux: package the assembled app folder directly. On Windows that
|
||||
// complements the Setup.exe installer (portable = unzip anywhere and run); on
|
||||
// Linux it is the ONLY artifact, since electrobun emits just the .tar.zst
|
||||
// updater blob there — no installer, AppImage or .deb. Best-effort: the folder
|
||||
// layout is confirmed by the first real build on each OS.
|
||||
if (requested === "win-x64" || requested === "linux-x64") {
|
||||
const appDir = path.join(ROOT, "build", `stable-${osName}-${archName}`, BRANDING.appName);
|
||||
// Windows: also zip the assembled app folder — the portable build (unzip
|
||||
// anywhere and run) next to the Setup installer. Best-effort: the folder
|
||||
// layout is confirmed by the first real 2.x Windows build.
|
||||
if (requested === "win-x64") {
|
||||
const appDir = path.join(ROOT, "build", "stable-win-x64", BRANDING.appName);
|
||||
if (existsSync(appDir)) {
|
||||
if (requested === "win-x64") {
|
||||
const portable = path.join(outDir, `${BRANDING.appName}-${buildName}-win-x64-portable.zip`);
|
||||
run(["powershell", "-Command", `Compress-Archive -Force -Path '${appDir}\\*' -DestinationPath '${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 {
|
||||
console.warn(`[release] WARN: portable skipped — app folder not found at ${appDir}`);
|
||||
}
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@
|
||||
header @feed Cache-Control "no-cache"
|
||||
|
||||
# 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"
|
||||
|
||||
# browse => directory listing; file_server serves range requests
|
||||
|
||||
+7
-5
@@ -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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
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.
|
||||
|
||||
## Notes
|
||||
|
||||
- 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
|
||||
static and always points at the latest build.
|
||||
- The page links to fixed, stable filenames (`macos-arm64-miniSCP.dmg`, `win-x64-miniSCP-Setup.zip`,
|
||||
…) 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
|
||||
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
@@ -3,7 +3,7 @@ set -euo pipefail
|
||||
# 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.:
|
||||
# bun scripts/release.ts # or --target mac-x64 / win-x64
|
||||
# bun scripts/release.ts # or --target win-x64 / linux-x64
|
||||
# site/deploy.sh
|
||||
#
|
||||
# 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}"
|
||||
mkdir -p "$dl"
|
||||
|
||||
# electrobun's flat artifacts (stable-<os>-<arch>-*): installers, update payloads,
|
||||
# update.json, and (once release.baseUrl is set) .patch files.
|
||||
if compgen -G 'artifacts/stable-*' >/dev/null; then
|
||||
cp -f artifacts/stable-* "$dl"/
|
||||
# electrobun's flat artifacts, verbatim — the updater contract, never rename them:
|
||||
# the update feed (stable-<os>-<arch>-update.json, .tar.zst, .patch) plus the
|
||||
# installers, which 2.x names without the channel prefix (macos-arm64-miniSCP.dmg,
|
||||
# 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
|
||||
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
|
||||
|
||||
# Windows portable zip is a release.ts extra (build-numbered) → give it a stable name.
|
||||
|
||||
+4
-5
@@ -215,9 +215,8 @@
|
||||
<h2 id="download">Download</h2>
|
||||
<p>Always the latest stable build.</p>
|
||||
<div class="downloads">
|
||||
<a class="dl" href="/dl/stable-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/stable-win-x64-miniSCP-Setup.zip">Windows<small>Installer</small></a>
|
||||
<a class="dl" href="/dl/macos-arm64-miniSCP.dmg">macOS<small>Apple Silicon</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-portable.zip">Windows<small>Portable</small></a>
|
||||
</div>
|
||||
<p class="browse">Looking for everything? <a href="/dl/">browse all files</a>.</p>
|
||||
@@ -226,8 +225,8 @@
|
||||
<h2 id="faq">FAQ</h2>
|
||||
<dl class="faqtable">
|
||||
<dt>Which platforms are supported?</dt>
|
||||
<dd>macOS (Apple Silicon and Intel) and Windows 11+ (x64). macOS is the primary target;
|
||||
Windows is currently in beta.</dd>
|
||||
<dd>macOS (Apple Silicon) and Windows 11+ (x64). macOS is the primary target;
|
||||
Windows is currently in beta. Intel Macs are not supported.</dd>
|
||||
|
||||
<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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Utils, type BrowserWindow } from "electrobun/bun";
|
||||
import { Utils, type BrowserWindow } from "electrobun/main";
|
||||
import { BRANDING } from "../shared/branding";
|
||||
import type { EditorOpenContext } from "../shared/rpc";
|
||||
import type { AppSettings } from "../shared/types";
|
||||
|
||||
+19
-2
@@ -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 type { EditorOpenContext } from "../shared/rpc";
|
||||
import { effectiveConcurrency, type AppSettings } from "../shared/types";
|
||||
@@ -55,6 +55,7 @@ const events = {
|
||||
openConnectionManager: () => {},
|
||||
openSettings: () => {},
|
||||
showAbout: () => {},
|
||||
applyUpdate: async () => {},
|
||||
};
|
||||
const rpc = createAppRPC({ registry, engine, store, settings, edits, watcher, events });
|
||||
// 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
|
||||
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) => {
|
||||
const transfers = engine.activeJobCount();
|
||||
const dirtyEditors = editors.dirtyCount();
|
||||
if (quitConfirmed || (transfers === 0 && dirtyEditors === 0)) return;
|
||||
(event as { response: { allow: boolean } }).response = { allow: false };
|
||||
const updating = applyingUpdate; // read now: it clears once the vetoed apply returns
|
||||
void (async () => {
|
||||
const parts = [
|
||||
transfers > 0 && `${transfers} transfer${transfers === 1 ? " is" : "s are"} still running`,
|
||||
@@ -233,7 +247,10 @@ Electrobun.events.on("before-quit", (event) => {
|
||||
});
|
||||
if (response === 0) {
|
||||
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,4 +1,4 @@
|
||||
import type { BrowserWindow } from "electrobun/bun";
|
||||
import type { BrowserWindow } from "electrobun/main";
|
||||
import type { AppSettings } from "../shared/types";
|
||||
import { createManagerRPC } from "./rpc-handlers";
|
||||
import type { ConnectionStore } from "./store/connections";
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/bun";
|
||||
import { ApplicationMenu, type ApplicationMenuItemConfig } from "electrobun/main";
|
||||
import { BRANDING } from "../shared/branding";
|
||||
import type { SavedConnection } from "../shared/types";
|
||||
import { t } from "./i18n";
|
||||
|
||||
@@ -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 { AppSettings, Result } from "../shared/types";
|
||||
import { readFileText, writeFileText, type EditManager } from "./edits";
|
||||
@@ -33,6 +33,8 @@ export interface AppDeps {
|
||||
openConnectionManager: () => void;
|
||||
openSettings: () => 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: () =>
|
||||
wrap(async () => {
|
||||
await Updater.applyUpdate(); // relaunches + quits; may never resolve
|
||||
await events.applyUpdate(); // relaunches + quits; may never resolve
|
||||
return {};
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { BrowserWindow } from "electrobun/bun";
|
||||
import type { BrowserWindow } from "electrobun/main";
|
||||
import type { AppSettings } from "../shared/types";
|
||||
import { t } from "./i18n";
|
||||
import { createSettingsRPC } from "./rpc-handlers";
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+7
-2
@@ -1,4 +1,4 @@
|
||||
import { BrowserWindow } from "electrobun/bun";
|
||||
import { BrowserWindow } from "electrobun/main";
|
||||
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
|
||||
* platforms keep the native titlebar. Constructor-only: there is no API to
|
||||
* 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() {
|
||||
return process.platform === "darwin"
|
||||
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 13 } }
|
||||
? { titleBarStyle: "hiddenInset" as const, trafficLightOffset: { x: 12, y: 4 } }
|
||||
: {};
|
||||
}
|
||||
|
||||
|
||||
Vendored
-3
@@ -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
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"extends": "./.hutch/devkit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": false,
|
||||
@@ -16,6 +17,6 @@
|
||||
"emitDecoratorMetadata": false,
|
||||
"skipLibCheck": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"include": ["src", "electrobun.config.ts"],
|
||||
"exclude": ["node_modules", "dist", "build", "../../package/dist"]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
* 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
|
||||
* whole right-click surface dead there. The menu is plain HTML now — one code
|
||||
* path on every platform — and still emits the exact same action strings, so
|
||||
* the dispatcher in app.ts is unchanged.
|
||||
* no-op on Linux (electrobun's own .hutch/devkit/api/sdks/main/proc/linux.md),
|
||||
* which left the whole right-click surface dead there. The menu is plain HTML
|
||||
* now — one code path on every platform — and still emits the exact same
|
||||
* action strings, so the dispatcher in app.ts is unchanged.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ContextMenuService {
|
||||
|
||||
@@ -76,9 +76,10 @@ export class RpcService {
|
||||
});
|
||||
const electroview = new Electroview({ rpc: this.rpc });
|
||||
|
||||
// Requests fired before the RPC WebSocket finishes connecting are lost, so
|
||||
// everything below gates on it being open (services fire calls at startup).
|
||||
const socket = electroview.bunSocket;
|
||||
// Gate everything below on the RPC WebSocket being open (services fire calls
|
||||
// at startup). v1 silently lost pre-open sends; 2.x Electroview queues them
|
||||
// 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) {
|
||||
this.ready = new Promise((resolve) => {
|
||||
socket.addEventListener('open', () => resolve(), { once: true });
|
||||
|
||||
+9
-3
@@ -8,14 +8,20 @@
|
||||
UI layer (services, platform, formatters). @core is an ALIAS rather than a
|
||||
relative path on purpose: skin components live at varying depths under
|
||||
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": {
|
||||
"@shared/*": ["../src/shared/*"],
|
||||
"@core/*": ["./src/app/core/*"]
|
||||
"@core/*": ["./src/app/core/*"],
|
||||
"electrobun/view": ["../.hutch/devkit/api/browser/index.ts"]
|
||||
},
|
||||
"noImplicitOverride": 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,
|
||||
"skipLibCheck": true,
|
||||
"isolatedModules": true,
|
||||
|
||||
Reference in New Issue
Block a user