Files
miniSCP/docs/build-and-release.md
jgamboaandClaude 0eb5608873 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>
2026-10-05 11:08:03 +02:00

12 KiB
Raw Permalink Blame History

Build & release

Versioning — "Build n"

Sublime-style: the version IS the commit count. scripts/gen-version.ts runs git rev-list --count HEAD + git rev-parse --short HEAD and writes the gitignored src/shared/version.gen.ts (BUILD = {number, commit, date, electrobun}). It is regenerated by postinstall and at the start of every dev/build/start/release script; committing it would bump the count it records, hence gitignored. Fresh checkouts fail tsc until bun install has run once (postinstall creates the file). BRANDING.version stays semver-shaped (0.1.<n>) for electrobun's config/updater; BRANDING.buildLabel ("Build n") is what humans see (About box, Settings → About).

Dev loop

  • 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.
  • 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|win-x64|linux-x64] (default: host). 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 — 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; 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 (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)

site/ is a self-contained landing page and download host, served by Caddy on an 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/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 *.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 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/* 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 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)

Wired on top of the download host above. See the "Auto-updater" section in architecture.md for the runtime flow.

  • Backend (src/bun/index.ts): Updater.checkForUpdate() runs ~5s after launch and every 6h; on updateAvailable it pushes rpc.send.updateStatus({phase:"available",…}). 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): requests checkForUpdate / downloadUpdate / applyUpdate (handlers in 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) holds signals (state/version/progress); the tab-bar "update pill" (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) routes a check-for-updates menu action into UpdaterService.check().
  • 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

scripts/gen-icons.ts (resvg, dev-time): renders assets/logo.svg into the macOS iconset + icon.png, a multi-size assets/icon.ico (Windows installer/ taskbar, wired via electrobun.config.ts win.icon), the macOS tray template PNG from assets/tray/tray-glyph.svg, and a small colored Windows tray PNG. Rerun after touching either SVG; outputs are committed (build inputs).

electrobun config notes

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 requires); 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.