Files
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

114 lines
4.8 KiB
TypeScript

// Build one release artifact for one target, on the matching host:
// 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";
const ROOT = path.join(import.meta.dir, "..");
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 "mac-arm64";
}
function fail(message: string): never {
console.error(`\n[release] ERROR: ${message}`);
process.exit(1);
}
function run(cmd: string[], opts: { cwd?: string } = {}): void {
console.log(`[release] $ ${cmd.join(" ")}`);
const proc = Bun.spawnSync(cmd, {
cwd: opts.cwd ?? ROOT,
stdout: "inherit",
stderr: "inherit",
});
if (!proc.success) fail(`command failed: ${cmd.join(" ")}`);
}
// ---- target selection + host guards ----
const argIndex = process.argv.indexOf("--target");
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.");
}
if (requested === "linux-x64" && process.platform !== "linux") {
fail("linux-x64 must be built on a Linux machine/runner — electrobun cannot cross-compile.");
}
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).");
}
// ---- prepare: version, devkit, icons, UI bundle ----
run(["bun", "scripts/gen-version.ts"]);
run(["bunx", "electrobun", "prepare"]);
run(["bun", "scripts/gen-icons.ts"]);
run(["bun", "run", "--cwd", "ui", "build"]);
const { BUILD } = await import("../src/shared/version.gen");
const { BRANDING } = await import("../src/shared/branding");
const buildName = `Build${BUILD.number}`;
// ---- electrobun build ----
// 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", "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}* installer found in artifacts/ — did the build run?`);
const out: string[] = [];
for (const file of produced) {
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: 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)) {
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 {
console.warn(`[release] WARN: portable skipped — app folder not found at ${appDir}`);
}
}
if (!out.length) fail("artifacts were found but none matched a known package extension.");
console.log(`\n[release] ${BRANDING.appName} ${BRANDING.buildLabel} (${BUILD.commit}) — ${requested}`);
for (const name of out) console.log(`[release] release/${requested}/${name}`);