SparkStartGuideReferencePlatformsCompare GitHub

Start

From nothing to a signed app on three systems.

Build it

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/.

A new app

./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;
});

Ship it

./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

What the signing words actually mean

Ad-hocWhat 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 IDWhat stops the first launch elsewhere saying the developer cannot be verified. Needs a certificate in the keychain.
NotarisedApple 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.

Check it everywhere

./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.

Keeping it up to date

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.