From nothing to a signed app on three systems.
git clone https://github.com/eddime/spark-native
cd spark-native
./spark build # this machine
./spark doctor # what this machine can build, and what is missing
macOS needs Xcode's command line tools. Linux needs X11, Xft and fontconfig
headers. Windows builds from either through zig cc, which
spark setup fetches into .spark/.
./spark new myapp
./spark dev myapp/index.html
spark new writes a whole app that already works: a layout, a
widget, a listener, a style and a script.
<body pad="24" gap="16" bg="#1a1a1a" size="480x300" title="My App">
<style name="card" bg="#222" radius="10" pad="16">
<text size="22" bold color="#fff">Hello</text>
<col use="card" gap="10" self="stretch">
<input id="who" placeholder="Your name">
<button id="greet">Greet</button>
<text id="out">…</text>
</col>
<script src="app.js"></script>
</body>
document.getElementById("greet").addEventListener("click", function () {
document.getElementById("out").textContent =
"Hello, " + document.getElementById("who").value;
});
./spark pack myapp/index.html # one file, no runtime beside it
./spark build all # every OS this machine can reach
./spark bundle myapp/index.html # .app on macOS, .desktop on Linux
./spark sign # ad-hoc, Developer ID, notarised
| Ad-hoc | What lets a binary start at all. An arm64 binary
with no signature is refused by the kernel, and lipo leaves the
universal binary unsigned — so the build does this by itself. |
| Developer ID | What stops the first launch elsewhere saying the developer cannot be verified. Needs a certificate in the keychain. |
| Notarised | Apple looking at it and saying so, with the
answer stapled on. One-time notarytool store-credentials, then
SPARK_NOTARY_PROFILE=… spark sign. |
Windows is the same shape with a .pfx, through
signtool or osslsigncode. Nothing invents
credentials: what is missing is named, with the command that provides it.
./spark cross myapp/index.html
The same document through macOS, a Linux container and Wine, with a layout
check and a screenshot from each. Two traps this walked into so you do not have
to: xvfb-run as PID 1 in a fresh container waits forever, and macOS
binaries cannot be cross-built anywhere else — that is a licence, not a
technical limit.
version 1.2.0
macos-arm64 https://example.com/myapp-macos-arm64 <sha256>
windows-x86_64 https://example.com/myapp.exe <sha256>
linux-x86_64 https://example.com/myapp-linux-x86_64 <sha256>
if (spark.updateCheck(url) !== VERSION && spark.updateApply(url))
tellTheUser("restart to finish");
Plain text, because a JSON parser would be larger than the feature. Fetching
is curl and hashing is shasum /
sha256sum / certutil — every system ships both, so
nothing is bundled here either.