Desktop
desktop/ is an Electron shell around the same web bundle. main.ts serves dist/ under
app://scmjs/, looks for a StarCraft installation and extracts the game data into the
app's data folder, opens maps handed over by the file manager, guards the window's close
while a map has unsaved changes, and holds the updater. preload.ts exposes that to the
page as window.scmjsDesktop, typed in src/gamedata/desktop.ts.
npm run desktop # bundle the main process and run Electron against dist/
SCMJS_DEV_URL=http://localhost:5173 npm run desktop # against the dev server instead
npm run build:desktop # web build + main bundle + electron-builder for this OS
npm run build:desktop takes the platform, architectures and targets on the command line;
with none it builds this OS with the targets electron-builder.yml lists, which is what
CI does:
npm run build:desktop -- win # Windows: nsis + zip
npm run build:desktop -- win nsis # just the installer
npm run build:desktop -- linux AppImage x64 arm64
npm run build:desktop -- mac dmg arm64
npm run build:desktop -- --dir # unpacked app, no installer: the fast check
npm run build:desktop -- win --skip-web # repackage the dist/ already on disk
npm run build:desktop -- --skip-plugins # leave plugins/ alone (no vendoring fetch)
Platforms are win, mac and linux; architectures x64, ia32, arm64, armv7l and
universal; any electron-builder target name (nsis, dmg, zip, AppImage, deb, …)
applies to every platform named. --skip-web and --skip-main reuse the bundles on disk;
--publish <mode> and anything after a bare -- go to electron-builder unchanged. The
script warns about cross-builds that need tooling the machine may lack (a macOS installer
off a Mac, an NSIS installer without Wine) and runs them anyway; --dir and zip
cross-build with nothing installed. Output lands in release/.
What ships. electron-builder.yml packs dist/ and desktop/dist/ only: never
node_modules (Vite bundles everything) and never the game data a developer's public/
may hold. Windows gets an NSIS installer and a zip, macOS a dmg per architecture, Linux an
AppImage and a .deb. The installers register .scm, .scx and .chk. Of Chromium's
own UI locales only those of the editor's languages are included (en-US and ko): the
editor's translations are in the bundle, but Chromium reports the system's language to the
page only when its locale is there. A new language needs its locale added to
electronLanguages. Builds are unsigned: Windows shows SmartScreen's "unrecognized app"
screen, and macOS refuses the app until the user clears the quarantine attribute.
What the build adds. THIRD-PARTY-NOTICES.txt in dist/ carries the licence of every
runtime dependency and compiled-in plugin, generated from package.json. The desktop
build and the container image also carry the runtimes the default plugins would otherwise
download on first use (none of today's defaults declares one; eudplib's Pyodide and wheel,
about 15 MB, did), so such a plugin needs no network there.
scripts/bundle-plugin-runtimes.mjs reads a runtime.json from each pinned default's
repository and puts the files in dist/plugin-runtime/<plugin>/<version>/. The hosted
editor and the web zip carry none.
The app's data folder is %APPDATA%\scm-js on Windows, ~/.config/scm-js on Linux
and ~/Library/Application Support/scm-js on macOS. It holds the extracted game data, the
window geometry, and startup.log, the milestones of the last launch. SCMJS_TRACE=1
echoes them to the terminal. When a launch "hangs and then opens", that log tells a slow
disk, a virus scanner and a slow first paint apart.
From WSL, copy a Windows build to the Windows file system before running it. Started
from \\wsl.localhost\…, a network share, it takes minutes to open. WSLENV passes the
variable on to the Windows process:
npm run build:desktop -- win --dir # release/win-unpacked
cp -r release/win-unpacked /mnt/c/Users/<you>/scmjs-test
SCMJS_TRACE=1 WSLENV=SCMJS_TRACE /mnt/c/Users/<you>/scmjs-test/scmJS.exe
In-app updates#
The desktop app checks GitHub releases at startup (a preference, on by default) and
through Help ▸ Check for Updates…, using electron-updater over the latest*.yml files the
release workflow uploads beside the installers. Finding one raises a toast with a Download
button; nothing downloads or installs unasked, and installing goes through the same
unsaved-changes check as closing the window. A nightly build follows the nightlies, a
stable build follows stable releases, and a preference lets a stable install follow the
nightlies.
Windows, the AppImage and the .deb install updates themselves. macOS can see an update
but not install it until the app is code-signed, so it offers the release page. A
development run and an unpacked Linux folder report that they cannot check.
desktop/updater.ts is the main-process side, src/editor/updates.ts the state machine,
tests/updates.test.ts the cases.