Files
miniSCP/docs/build-and-release.md
T
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

191 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](architecture.md) for the runtime flow.
- **Backend** ([src/bun/index.ts](../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](../src/shared/rpc.ts)): requests `checkForUpdate` / `downloadUpdate` /
`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 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 `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.