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:
+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.
|
||||
|
||||
Reference in New Issue
Block a user